488 Commits

Author SHA1 Message Date
DanB
1d622cbd14 Fix package script to properly include freeswitch tutorial samples 2014-08-18 13:41:32 +02:00
DanB
3787ee64d5 Fix tut_fs_json/freeswitch config files 2014-08-18 12:16:15 +02:00
DanB
cf02179cde TutOpenSIPS documentation improvements 2014-08-18 11:39:36 +02:00
DanB
35e120e3f6 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-18 11:14:49 +02:00
DanB
5c36dbc62e Updating tutorials with OpenSIPS event 2014-08-18 11:14:08 +02:00
Radu Ioan Fericean
0f95e986a7 fixed typo 2014-08-17 19:19:28 +03:00
DanB
616a81279e Updating documentation with cdrstats, derived_charging 2014-08-17 14:23:17 +02:00
DanB
988f3c6bf2 Fix RPAlias loading 2014-08-16 20:16:17 +02:00
DanB
53f19dad4a Updating tutorials documentation, modified tariffplans/tutorial files 2014-08-16 13:21:34 +02:00
DanB
d1bd2700fd Adding cdrstats_* verification commands in the cgrates_usage tutorial, updating freeswitch vanilla samples with latest of 1.4 2014-08-15 19:06:37 +02:00
DanB
e45742761e More tutorial documentation refactoring 2014-08-15 15:31:05 +02:00
DanB
410ecfdd43 More tutorial documentation refactoring 2014-08-15 14:57:58 +02:00
DanB
36ae05cde2 FreeSWITCH Tutorial refactoring 2014-08-15 14:25:54 +02:00
DanB
b24c1982bf Adding StatsQueueTriggered to enhance the information displayed in triggered actions for StatsQueue 2014-08-15 13:20:44 +02:00
DanB
54e7c8a971 Adding cdrstats metrics to email and log actions 2014-08-13 18:05:05 +02:00
Radu Ioan Fericean
af4b38ffd5 removed goroutine action exec from scheduler 2014-08-13 17:00:16 +03:00
Radu Ioan Fericean
6c6b27a9d9 fix for local init it test files 2014-08-13 16:49:58 +03:00
Radu Ioan Fericean
75c8253756 made map storage behave more like redis one 2014-08-13 16:49:58 +03:00
DanB
f9204b54aa Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-13 14:38:13 +02:00
DanB
bba82e7c91 SM-FreeSWITCH: process all CDRs instead of just prepaid/pseudoprepaid, adding cdrstats to tariffplans/tutorial folder 2014-08-13 14:38:08 +02:00
Radu Ioan Fericean
b287c82ec3 removed drone.io badge 2014-08-13 14:47:37 +03:00
Radu Ioan Fericean
98996edc7e drone.io is out of service 2014-08-13 14:44:13 +03:00
Radu Ioan Fericean
6342c4a3e5 save *asap action timings with empty account id list 2014-08-13 14:38:52 +03:00
DanB
c64dae59d4 Adding GetId method to StatsQueue, mailAsync action improvements 2014-08-06 17:33:35 +02:00
DanB
29eff410e7 Putting back package files 2014-08-06 01:47:23 +02:00
DanB
1ff2af86d4 Fix Thresholds not being calculated for new balances 2014-08-06 01:45:32 +02:00
DanB
514df9a489 Fix nil pointer on timespans which are not paid 2014-08-05 23:24:20 +02:00
DanB
0a6f0ab0c7 Fixups osips_event tutorial 2014-08-05 22:41:56 +02:00
DanB
c0b18e3ae6 Config files for cgrates in osips_event tutorial 2014-08-05 19:07:06 +02:00
DanB
48d5866ea1 Adding opensips startup scripts for osips_event tutorial 2014-08-05 18:59:10 +02:00
DanB
e9ba47beab Individual configuration in sm/freeswitch for capturing extra fields 2014-08-05 18:17:10 +02:00
DanB
99eb85f8bc Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-05 16:03:20 +02:00
DanB
71a932e343 FS Event parsing cgr_category instead of cgr_tor, zero FS configuration support for passive billing scenarios 2014-08-05 16:03:02 +02:00
DanB
33e3f3fd66 Sql Flush command implementation to be used in tests, local tests for tutorial data 2014-08-05 01:09:05 +02:00
DanB
bb9e77c688 Fix cgr-tester.py script 2014-08-04 14:57:42 +02:00
DanB
ac3bbfe5a2 Fix tester 2014-08-04 14:33:12 +02:00
DanB
6e94842539 Fix in CgrTester to properly reflect TOR and Category 2014-08-04 13:48:22 +02:00
DanB
b28b8a60dd Dynamic setup_time on cdrs used in CdrStats local tests 2014-08-04 13:26:18 +02:00
DanB
88fdcbfca8 Updating sample osips_event with local addresses used in test 2014-08-04 13:03:14 +02:00
DanB
edb783ab39 Adding osips_event tutorial configurations 2014-08-03 16:55:08 +02:00
DanB
90fa1465ac Fixup concurrency on mediation runs and stats, improving tutorial CdrStats files 2014-08-03 16:04:43 +02:00
DanB
455379ef97 cgr-loader information formated with tab for verbose mode, adding data/tariffplans/tutorial folder to use as common tutorial tariff plan data 2014-08-02 22:24:39 +02:00
DanB
a48fa90dd8 Fix balance thresholds not being executed 2014-08-02 21:13:11 +02:00
DanB
a290a6aa6e cgr-loader to automatically reload cdrstats data 2014-08-02 18:30:22 +02:00
DanB
795a799ac2 Sample tariffplan for CDRStats 2014-08-02 17:19:57 +02:00
DanB
f2efe6fe9a Local tests for CDRStatsV1 2014-08-02 17:19:20 +02:00
Radu Ioan Fericean
4d78c0a63b more tests on stats 2014-08-02 17:51:12 +03:00
Radu Ioan Fericean
97c4cf5bd6 test for stats RPC methods 2014-08-02 15:33:17 +03:00
DanB
bcf4c3d08e StoreDisable implementation for both CDRS and Mediator 2014-08-02 13:58:15 +02:00
DanB
f72568054b Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-02 13:27:44 +02:00
DanB
cabcd67559 Config options to disable storing of the CDRs in CDRS and Mediator 2014-08-02 13:27:33 +02:00
Radu Ioan Fericean
a6b82806fd console now can receive params with array value 2014-08-02 13:35:57 +03:00
DanB
7c34c36f6f Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 21:07:40 +02:00
Radu Ioan Fericean
f8bb5d4d66 fix trigger execution 2014-08-01 22:06:47 +03:00
DanB
bdc127b76d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 21:03:45 +02:00
Radu Ioan Fericean
909ceb2759 compilation erro fix 2014-08-01 22:01:33 +03:00
DanB
5a080846e7 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:59:43 +02:00
Radu Ioan Fericean
9adebc6b10 fixed trigger selection 2014-08-01 21:58:56 +03:00
DanB
09acd19fb7 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:47:15 +02:00
Radu Ioan Fericean
dd08456d13 keep *default queue on reload and revive rounding 2014-08-01 21:43:01 +03:00
DanB
b29abe35a4 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:36:03 +02:00
Radu Ioan Fericean
b86a8c77ba no more rounding 2014-08-01 21:35:43 +03:00
DanB
9feb8c47db Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:26:03 +02:00
DanB
c63a9a2221 Fix cdrstats api 2014-08-01 20:25:55 +02:00
Radu Ioan Fericean
5496a0eae2 better update queues 2014-08-01 21:25:39 +03:00
DanB
2411534ede Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:14:54 +02:00
Radu Ioan Fericean
3fcf8716e0 check for len ==0 instead of nil 2014-08-01 21:13:41 +03:00
DanB
146ef48435 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:05:17 +02:00
Radu Ioan Fericean
aade719f27 fix build error 2014-08-01 21:05:01 +03:00
DanB
25d0715a70 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:04:25 +02:00
Radu Ioan Fericean
7cbf5c2b56 initialize queue ids slice 2014-08-01 21:03:31 +03:00
DanB
2a953fcc33 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 20:01:43 +02:00
Radu Ioan Fericean
d07d772013 premature optimization 2014-08-01 20:57:11 +03:00
DanB
7a9d28d8f5 Fix conflicts 2014-08-01 19:29:48 +02:00
DanB
3a09524aa0 Fix Reset command 2014-08-01 19:28:54 +02:00
Radu Ioan Fericean
0b7fcf2167 first draft for reload and reset queues 2014-08-01 20:27:55 +03:00
Radu Ioan Fericean
a1e562e248 Added ResetQueue and cleaned cdrs on queue conf update 2014-08-01 20:17:02 +03:00
DanB
39379bf64a CDRStats.ResetQueues API Command 2014-08-01 19:03:53 +02:00
Radu Ioan Fericean
6dfaca4443 added ReloadQueues method 2014-08-01 19:47:16 +03:00
DanB
452b794f6d CDRStats.ReloadStatsQueues API method 2014-08-01 18:38:42 +02:00
Radu Ioan Fericean
605fde865a moved stats from accounting to rating db 2014-08-01 18:57:04 +03:00
Radu Ioan Fericean
6634751d7a cdrstats_metrics and cdrstats_queueids console commands 2014-08-01 18:47:52 +03:00
DanB
8d1be00962 Fixes local_tests 2014-08-01 17:30:22 +02:00
Radu Ioan Fericean
a096d97d94 added GetQueueIds and expire at GetValues 2014-08-01 17:59:13 +03:00
Radu Ioan Fericean
1574de1d38 some cache loking fixes 2014-08-01 17:59:13 +03:00
DanB
3e0e30f2cc Calculate stats after mediation storing the results in stordb 2014-08-01 15:36:37 +02:00
DanB
97977e98cf Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 13:38:02 +02:00
Radu Ioan Fericean
2527ad2970 fix hang on empty metrics 2014-08-01 14:37:16 +03:00
DanB
4779842574 Fix autodetection of IP subscribed to opensips 2014-08-01 13:16:48 +02:00
DanB
6092df2f76 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-08-01 12:56:14 +02:00
DanB
ba3eb3dda4 CdrStatsConfig with defaults 2014-08-01 12:52:43 +02:00
Radu Ioan Fericean
9e6d6a7b07 check for session manager initialized when shutdown 2014-08-01 13:00:40 +03:00
Radu Ioan Fericean
0033f400b2 fix get cdrstats from redis and a small rpc problem 2014-08-01 12:52:14 +03:00
Radu Ioan Fericean
8b77f457d2 initial length for queue map 2014-08-01 12:31:00 +03:00
DanB
63eafcab20 Adding OpenSIPS sample config for CDRStats 2014-08-01 11:27:18 +02:00
Radu Ioan Fericean
fdde40d312 fix for nil pointer in UpdateQueues 2014-08-01 12:20:37 +03:00
DanB
eef3a43016 Instantiating stats queue out of default config 2014-08-01 10:49:45 +02:00
Radu Ioan Fericean
8c54ad63e7 compilation fix for tpimporter 2014-07-31 22:57:47 +03:00
DanB
7ed24141b6 Default CdrStatsInstance configuration parsing 2014-07-31 19:32:54 +02:00
DanB
ac9b244eed Merge branch 'master' of https://github.com/cgrates/cgrates 2014-07-31 17:54:57 +02:00
DanB
b57fd3b09a Adding cdrstats config options 2014-07-31 17:54:27 +02:00
Radu Ioan Fericean
6d03010858 fixed triigers for tpimporter 2014-07-31 17:40:54 +03:00
DanB
67d6bdb4d8 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-07-31 15:28:45 +02:00
DanB
ed098e4665 Adding apier.CDRStatsV1 type and config.CdrStatsConfig 2014-07-31 15:28:42 +02:00
Radu Ioan Fericean
d6ede3560a more refactorings 2014-07-31 15:05:35 +03:00
Radu Ioan Fericean
d29512de02 moved AcceptCDR method into CdrStats 2014-07-31 15:00:56 +03:00
Radu Ioan Fericean
ebc972ed41 better stats methods 2014-07-31 14:55:47 +03:00
Radu Ioan Fericean
98cdbb6dc0 update cdr stats on engine start 2014-07-31 13:17:01 +03:00
Radu Ioan Fericean
26b1e8685b added all triggers to csv test 2014-07-30 20:15:12 +03:00
Radu Ioan Fericean
5400b2f44b rating subject instead of rate subject 2014-07-30 19:59:41 +03:00
Radu Ioan Fericean
1ae632ba80 5d fix 2014-07-30 19:58:05 +03:00
Radu Ioan Fericean
bbb116b699 fix for set tp triggers 2014-07-30 19:54:39 +03:00
Radu Ioan Fericean
ee64e5a0df fixed Scan 2014-07-30 19:36:57 +03:00
Radu Ioan Fericean
b2100a354a removed duplicate column 2014-07-30 19:32:13 +03:00
Radu Ioan Fericean
9c2925894b renamed column 2014-07-30 19:29:58 +03:00
Radu Ioan Fericean
bd6ed7e66a sql syntax fix 2014-07-30 19:27:44 +03:00
Radu Ioan Fericean
f86ab09580 initialize dests map 2014-07-30 19:23:28 +03:00
Radu Ioan Fericean
2250d9a2cc regxsp++ 2014-07-30 19:18:59 +03:00
Radu Ioan Fericean
2c72e2e7c0 more regexp fixes 2014-07-30 19:14:18 +03:00
Radu Ioan Fericean
1474516d9a fixed tarrif plans 2014-07-30 19:06:24 +03:00
Radu Ioan Fericean
6742345026 Merge branch 'master' into stats 2014-07-30 18:42:54 +03:00
Radu Ioan Fericean
91ac0fd3f2 more fixes for action trigger db load 2014-07-30 18:41:45 +03:00
DanB
7bff0db7d2 Small fixup sample opensips.cfg 2014-07-30 17:34:24 +02:00
DanB
7abf8cb12c SM-OpenSIPS with AUTHORIZATION for pseudoprepaid support implementation, sample opensips.cfg configuration 2014-07-30 17:28:59 +02:00
Radu Ioan Fericean
9c4d3439bb more triggerx regexp fixes, started load db 2014-07-30 17:03:14 +03:00
Radu Ioan Fericean
b012922272 test for cdr stats csv load 2014-07-30 14:23:31 +03:00
Radu Ioan Fericean
8342a00fcb added rated subject and account to cdrstats 2014-07-29 21:32:29 +03:00
Radu Ioan Fericean
8ee8962328 renamed QueuedItems to QueueLenth 2014-07-29 17:14:05 +03:00
DanB
1f28e2bc2c Fix OpenSIPS Event time parsing 2014-07-29 12:51:48 +02:00
DanB
0cc9b0caff Verbose test 2014-07-29 12:00:31 +02:00
Radu Ioan Fericean
72d38127f9 added one more csv file 2014-07-29 12:53:48 +03:00
Radu Ioan Fericean
f2da780893 compilation fix 2014-07-29 12:48:56 +03:00
Radu Ioan Fericean
4ea9d8f35a Merge branch 'master' into stats
Conflicts:
	apier/mediator.go
	cmd/cgr-engine/cgr-engine.go
	config/config.go
	config/config_test.go
	config/test_data.txt
	engine/cdrs.go
	engine/mediator.go
	general_tests/fsevcorelate_test.go
	test.sh
	utils/apitpdata.go
	utils/consts.go
2014-07-29 12:32:03 +03:00
DanB
2bab5f0585 OsipsDagram as part of packaging 2014-07-28 20:06:38 +02:00
DanB
fc546fe6b6 Adding FSMinDurLowBalance, FSLowBalanceAnnFile,FSEmptyBalanceContext, FSEmptyBalanceAnnFile and logic behind to support playing annoucements and transfers before disconnecting prepaid calls within FreeSWITCH SessionManager 2014-07-28 20:02:51 +02:00
DanB
7fff0b2aad Merge branch 'OsipsSM' 2014-07-28 18:15:05 +02:00
DanB
a541d32642 Adding SuppressCgrIds parameter on CdrExporter to prevent CgrIds reporting in replies 2014-07-28 16:11:46 +02:00
DanB
1d1aa5d3e1 Adding SupressCgrIds option to disable verbose reporting of Ids exported/not exported 2014-07-28 16:05:32 +02:00
DanB
9b2b3d2c94 OpenSIPS Events autosubscribe from SM 2014-07-28 16:01:26 +02:00
DanB
0f6e8540ef SM-OpenSIPS: configuration options in .cfg file 2014-07-27 12:24:28 +02:00
DanB
9cfe1a9864 Method to generate NewOsipsEvent 2014-07-27 11:28:36 +02:00
DanB
6cb79b5585 Connection to CDRS component and first CDR from OpenSIPS processed 2014-07-27 10:30:20 +02:00
DanB
dcb4fa77e0 Adding CdrHost to StoredCdr out of OsipsEvent 2014-07-27 10:21:25 +02:00
DanB
96d1775dd1 Adding AsStoredCdr method 2014-07-26 17:05:12 +02:00
DanB
a1d96d805e Initial SM-OpenSIPS and OpenSIPSEvent parsing with tests 2014-07-26 14:23:36 +02:00
DanB
e93b8a6c6f engine.Responder with ProcessCdr method, moved cdrs and mediator to engine 2014-07-25 17:44:50 +02:00
DanB
19e994ca8a Adding CDRSV1.ProcessCdr API to feed cdrs via RPC 2014-07-25 12:25:23 +02:00
DanB
4f5894b27e Cdrc now supports multiple field sources 2014-07-24 17:32:46 +02:00
DanB
36282cbc17 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-07-23 20:51:16 +02:00
DanB
f092d517df MediatorV1.RateCdrs with more filter options, renaming cdr arguments to reflect plurals properly 2014-07-23 19:53:52 +02:00
Radu Ioan Fericean
1091f58297 cdr stats csv load (without tests) 2014-07-22 22:10:39 +03:00
Radu Ioan Fericean
8fe59a2a3f Closed bracket 2014-07-22 10:30:15 +03:00
Radu Ioan Fericean
38b3dad5b0 Added webchat 2014-07-22 10:26:58 +03:00
Radu Ioan Fericean
c58684f99e removed unused rpc parameter 2014-07-22 10:14:08 +03:00
DanB
48bbe1af89 FS SessionManager - Timeout should be after connect, ticket #26 2014-07-21 19:21:25 +02:00
Radu Ioan Fericean
3dc0d44f4b compilation fix 2014-07-21 20:20:16 +03:00
Radu Ioan Fericean
8a2adb5e61 SendToStats parameter for mediator methods 2014-07-21 20:19:07 +03:00
Radu Ioan Fericean
8ac8b65a95 cdrs stats RPC service and integration with cdrs/mediator 2014-07-21 20:00:58 +03:00
DanB
71464ce65e Fixup min_callduration being used by max_callduration parsing 2014-07-18 15:09:06 +02:00
DanB
fd532fb1c3 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-07-17 20:42:42 +02:00
DanB
5e75234ede Query filters for ratedAccount and ratedSubject 2014-07-17 20:29:13 +02:00
Radu Ioan Fericean
f6d0454a81 added reset filters for action trigger reset
+test
2014-07-17 19:42:03 +03:00
Radu Ioan Fericean
1289adc155 added reset filters for action trigger reset
+test
2014-07-17 19:40:08 +03:00
Radu Ioan Fericean
a5bf017085 test balance match action trigger 2014-07-17 19:16:15 +03:00
Radu Ioan Fericean
ca4988dd77 Merge branch 'master' into stats
Conflicts:
	engine/action_trigger.go
2014-07-17 19:01:43 +03:00
Radu Ioan Fericean
93ec058b19 added RatingSubject and SharedGroup to action trigger 2014-07-17 19:00:12 +03:00
DanB
ae78f8fba0 Adding FieldAsString method to extract RatedAccount and RatedSubject, tests 2014-07-17 16:15:25 +02:00
DanB
b8eb3fa513 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-07-17 14:27:47 +02:00
DanB
5430490ec5 Adding RatedAccount and RatedSubject inside StoredCdr 2014-07-17 14:27:06 +02:00
Radu Ioan Fericean
362f34fe21 added parse command 2014-07-17 14:11:13 +03:00
Radu Ioan Fericean
982f6e0864 faster lock unlock 2014-07-17 13:33:17 +03:00
Radu Ioan Fericean
a42015a461 renamed BalanceKey to AccountKey 2014-07-17 11:56:13 +03:00
Radu Ioan Fericean
25da31a301 updated apier for expiration date 2014-07-17 11:40:07 +03:00
Radu Ioan Fericean
cb9e69f5c9 Merge branch 'master' into stats
Conflicts:
	engine/action_trigger.go
2014-07-17 11:16:24 +03:00
DanB
62d1a31290 Removing unique key in cdrs_extra since it only slows down INSERTs 2014-07-16 11:50:43 +02:00
DanB
3c4a247052 Adding min_callduration option in session manager 2014-07-16 11:34:51 +02:00
DanB
c08fc13629 Removing Type out of CgrXmlConfiguration 2014-07-16 11:14:45 +02:00
DanB
a449194b62 CdrExporter with configurable field separator, counters for sms and data usage 2014-07-16 10:56:56 +02:00
DanB
5a1994a943 Apier.AddTriggeredAction receiving now BalanceWeight and ExpiryTime 2014-07-16 09:36:15 +02:00
DanB
c92affde6d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-07-15 19:47:01 +02:00
DanB
a722d3242b Concurent import for CDR files 2014-07-15 19:46:53 +02:00
Radu Ioan Fericean
8aa5540d23 action's execute now get stats queue 2014-07-15 18:33:46 +03:00
Radu Ioan Fericean
e6a6910a46 removed cdrstats dir 2014-07-15 16:38:54 +03:00
Radu Ioan Fericean
84dffb963a Merge branch 'master' into stats
Conflicts:
	cmd/cgr-engine/cgr-engine.go
	engine/action_trigger.go
	utils/rsrfield.go
2014-07-15 16:37:23 +03:00
Radu Ioan Fericean
c44287bee0 quick fix for compile error 2014-07-15 16:16:45 +03:00
Radu Ioan Fericean
8023de5f24 action trigger balance weight and exp time
also execute action trigger only on dirty balances
2014-07-15 16:14:58 +03:00
Radu Ioan Fericean
26fac17b33 started stats triggers 2014-07-15 15:43:33 +03:00
DanB
430da9b714 CdrExporter does not longer make differences between .csv and .fwv formats handling, treating fields mandatory property also 2014-07-15 11:59:46 +02:00
Radu Ioan Fericean
77da8b0daf various stats tests 2014-07-14 14:41:53 +03:00
DanB
31a573fde4 RSRField parser to accept one letter replace templates 2014-07-11 18:22:50 +02:00
DanB
e14e28b31c Removing debug log 2014-07-11 18:12:51 +03:00
DanB
e09212354c Adding re-connecting rpc client 2014-07-11 18:12:51 +03:00
DanB
5daf99f875 DerivedCharging run filters are now chained 2014-07-11 18:12:51 +03:00
DanB
a62ab32bbc Merging old ParseRSRFields from config with new one in utils 2014-07-11 18:12:51 +03:00
DanB
8c23a91f05 Logic to handle multiple derived charging run filters inside mediator and session manager 2014-07-11 18:12:51 +03:00
DanB
4b77ca7acd Adding ev.PassesFieldFilter tests 2014-07-11 18:12:51 +03:00
DanB
6a91c174c1 fsevent.ParseEventValue tests refactored to cope with time differences on different build machines 2014-07-11 18:12:51 +03:00
DanB
8017af8aab Changed cgrid hash to build using SetupTime.UTC() 2014-07-11 18:12:51 +03:00
DanB
50750e2dc2 Small test fix 2014-07-11 18:12:50 +03:00
DanB
511fcfd8f5 Adding ParseRSRFields for list of RSRFields, modifying NewRSRField static format to add suffix / 2014-07-11 18:12:50 +03:00
DanB
14c613150f StoredCdr.PassesFieldFilter with grouping in regexp rules, fsevent with PassesFieldFilter method for derived charging 2014-07-11 18:12:50 +03:00
DanB
b439ff9f92 Removing debug log 2014-07-11 18:12:50 +03:00
DanB
4ea3ac49da Allow derived charging regexp filters with empty templates 2014-07-11 18:12:50 +03:00
DanB
10374448e6 Disabling derived charging filter in session manager until better implementation 2014-07-11 18:12:50 +03:00
DanB
d1807386c6 Disabling debug message for derived charging filter in mediator 2014-07-11 18:12:50 +03:00
DanB
28c27c8863 RegexpSearchReplace rule has now Matched field to confirm matching, DerivedChargers has now Regexp support in filters 2014-07-11 18:12:50 +03:00
DanB
57110e81c8 Loader for derived charging filters, using derived chargers filters within mediator and session manager, filter implementation inside StoredCdr 2014-07-11 18:12:50 +03:00
DanB
c904343280 RunFilter in DerivedChargers, ParseEventValue accepting RSRField in FreeSWITCH event 2014-07-11 18:12:49 +03:00
DanB
392071da85 Cdrc to accept static fields in case of .csv files 2014-07-11 18:12:49 +03:00
DanB
bb102e65c2 Derived charging - fix nil pointer dereference in case of empty fields on fork cdr, cdrexporter correctly returning now empty file path in case of no cdrs to be exported 2014-07-11 18:12:49 +03:00
DanB
e9da9fd565 Fix CDRExporter to avoid writing empty fwv files, cgr-loader now orders rating recache in case of flushdb option used 2014-07-11 18:12:49 +03:00
DanB
c8c52842cb Removing debug log 2014-07-11 17:00:19 +02:00
DanB
192ce121de Adding re-connecting rpc client 2014-07-11 16:40:38 +02:00
DanB
d47e1bf53b DerivedCharging run filters are now chained 2014-07-11 10:28:01 +02:00
DanB
e808168d52 Merging old ParseRSRFields from config with new one in utils 2014-07-09 17:49:25 +02:00
DanB
22670e12a8 Logic to handle multiple derived charging run filters inside mediator and session manager 2014-07-09 15:21:42 +02:00
DanB
0986c1371b Adding ev.PassesFieldFilter tests 2014-07-09 13:39:57 +02:00
DanB
3a8d78e798 fsevent.ParseEventValue tests refactored to cope with time differences on different build machines 2014-07-09 12:57:06 +02:00
DanB
b5f26c00b8 Changed cgrid hash to build using SetupTime.UTC() 2014-07-09 12:31:45 +02:00
DanB
090f7706e8 Small test fix 2014-07-09 11:39:10 +02:00
DanB
6d00cce055 Adding ParseRSRFields for list of RSRFields, modifying NewRSRField static format to add suffix / 2014-07-08 18:55:37 +02:00
DanB
7bdd49b649 StoredCdr.PassesFieldFilter with grouping in regexp rules, fsevent with PassesFieldFilter method for derived charging 2014-07-08 14:32:20 +02:00
DanB
03fa289a8d Removing debug log 2014-07-07 19:26:59 +02:00
DanB
f77e6f03f3 Allow derived charging regexp filters with empty templates 2014-07-07 19:22:43 +02:00
DanB
a13ebf828c Disabling derived charging filter in session manager until better implementation 2014-07-07 18:17:09 +02:00
DanB
362d2d4b22 Disabling debug message for derived charging filter in mediator 2014-07-07 18:16:21 +02:00
DanB
e96251aba3 RegexpSearchReplace rule has now Matched field to confirm matching, DerivedChargers has now Regexp support in filters 2014-07-07 17:48:59 +02:00
DanB
31b421c755 Loader for derived charging filters, using derived chargers filters within mediator and session manager, filter implementation inside StoredCdr 2014-07-07 11:34:23 +02:00
DanB
9b37e97cc2 RunFilter in DerivedChargers, ParseEventValue accepting RSRField in FreeSWITCH event 2014-07-04 20:05:27 +02:00
DanB
a014ef8401 Cdrc to accept static fields in case of .csv files 2014-07-03 19:50:14 +02:00
DanB
a4b3d94431 Derived charging - fix nil pointer dereference in case of empty fields on fork cdr, cdrexporter correctly returning now empty file path in case of no cdrs to be exported 2014-07-02 19:39:19 +02:00
Radu Ioan Fericean
03dd27b8d8 added cdrstats to test.sh and started getstats 2014-07-01 23:33:08 +03:00
DanB
ff7f4cac09 Fix CDRExporter to avoid writing empty fwv files, cgr-loader now orders rating recache in case of flushdb option used 2014-07-01 18:27:12 +02:00
Radu Ioan Fericean
38877ca8f9 building new stats packages 2014-07-01 16:23:14 +03:00
Radu Ioan Fericean
2fef2248e1 started cdr stats 2014-07-01 15:16:11 +03:00
DanB
a25fc94d45 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-06-28 14:20:25 +02:00
DanB
4a21ed9684 CdrStatsConfig.ProcessedStats -> Metrics 2014-06-28 14:20:17 +02:00
Radu Ioan Fericean
8e505fdbd9 added a placeholder go file for 1.3 to compile 2014-06-24 17:53:00 +03:00
Radu Ioan Fericean
bcb9479ee5 removed duplicated line 2014-06-24 17:52:24 +03:00
cgrates
fcecc823d0 added all supported go versions to travis 2014-06-22 10:12:07 +03:00
cgrates
704fae01ce Switched to go 1.3 for travis 2014-06-22 09:58:41 +03:00
Radu Ioan Fericean
1edd4f13d8 docker files for development and production testing 2014-06-21 16:16:46 +03:00
DanB
3d27154b91 callcost->cost_details 2014-06-19 13:36:49 +02:00
DanB
0f5b998c82 Fix cdrexporter processing the stats before built 2014-06-19 11:13:16 +02:00
DanB
056b9c6c61 Fixup Responder.GetDerivedCharging, adding localtests for both ApierV1.GetDerivedCharging and Responder.GetDerivedCharging 2014-06-17 12:51:18 +02:00
DanB
d3835067cf Session manager fixups for derived charging 2014-06-16 17:58:25 +02:00
DanB
222602dfff Postpaid calls no longer handled by session manager 2014-06-16 11:26:05 +02:00
DanB
bc5a80fe25 Adding *voice balance and derived charging inside fs_json tutorial, fixup loading of derived charging from csv 2014-06-15 19:05:00 +02:00
DanB
c973ea99e2 Fixups CdrExporter, derived charging should not transfer but kill the session if errors onAnswer 2014-06-15 12:22:06 +02:00
DanB
ff714a1bc8 Small sql fixup on missing cdr_extra values 2014-06-12 11:18:21 +02:00
DanB
c8ce2ddce2 Adding datetime field to CdrExporter 2014-06-11 19:47:25 +02:00
DanB
fe06ede121 Failed charging test 2014-06-07 13:36:56 +02:00
DanB
196978bcec Adding combimed cdrexporter fields, filter parameter for cdrfields 2014-06-07 12:49:12 +02:00
DanB
cc585cd86e Adding filter value to cdre config 2014-06-06 12:30:54 +02:00
DanB
dac4300726 Adding sleep parameter to CdrStats triggers 2014-06-06 10:50:53 +02:00
DanB
6b1a67d4f5 Expanding CallStatsConfig 2014-06-05 20:04:44 +02:00
DanB
c4a463606a Add RatedCdrs parameter in CdrStatsConfig 2014-06-05 13:01:59 +02:00
DanB
4af3af33c5 Initial CdrStatsConfig sample 2014-06-05 12:58:44 +02:00
DanB
4545ca1a30 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-06-05 11:06:51 +02:00
DanB
f9f994cd7e HttpSkipTlsVerify for http client 2014-06-05 11:02:17 +02:00
Radu Ioan Fericean
3d99f917f5 renamed balance_add to balance_set
now it accepts negative values
2014-06-04 18:43:29 +03:00
Radu Ioan Fericean
b56356486f balance debit return type 2014-06-04 15:25:23 +03:00
DanB
7bd66530fe Fixup balance_debit command, cdrexporter supporting http_post field values 2014-06-04 13:46:53 +02:00
DanB
8e7eb8233f Fixup default width parameters for cdre config 2014-06-04 12:21:20 +02:00
DanB
f436346873 Refactored CDRExporter to merge common exports into one mechanism, added CdreConfig type to collect configuration for CDRE from more sources 2014-06-03 19:05:36 +02:00
DanB
c063bc2a21 Adding utils.HttpJsonPost client 2014-05-28 19:33:47 +02:00
DanB
1e92a6795f Merge branch 'master' of https://github.com/cgrates/cgrates 2014-05-28 12:54:25 +02:00
DanB
18629002a6 StoredCdr.AsCgrCdrOut() with tests 2014-05-28 12:54:03 +02:00
Radu Ioan Fericean
b62d7ba52d fallback on *any destination 2014-05-28 13:53:49 +03:00
DanB
7b2a452c4f Correcting sms test 2014-05-28 10:17:49 +02:00
DanB
e15a08d633 Adding SMS Charging test 2014-05-28 10:12:38 +02:00
DanB
4f95f56284 Usage stored as decimal in MySQL, more tests and sample tariff plan data 2014-05-28 09:36:16 +02:00
DanB
42e9719fed DurationMangle is now bidirectional, cdr exporter properly formating duration 2014-05-26 14:16:35 +02:00
DanB
31881ea41c StoredCdr.FormatUsage now considers Data, StoredCdr.MangleDataUsage to properly export CDR objects 2014-05-26 12:58:00 +02:00
DanB
e6aaf24a14 Small rule fixup regexp + prefixing E164 numbers 2014-05-25 19:35:41 +02:00
DanB
00248b16dc General tests and configs for multiple cdrc imports 2014-05-25 16:23:05 +02:00
DanB
dbf6379818 Adding TOR in cgrates.cfg, fixing tests 2014-05-25 12:23:57 +02:00
DanB
93fa37b20b Including TOR field in cdrc, tests fixups 2014-05-25 11:16:52 +02:00
DanB
9bf5f7611b xmlcdrc with setDefaults(), modifying default order of imported and exported cdrs 2014-05-23 21:44:33 +02:00
DanB
5fe65f203c Adding compressed date string parsing, sample multiple cdrc with fw exporter as .xml 2014-05-21 20:10:40 +02:00
DanB
ad4fedc197 Fork cdrc over multiple folders based on xml configuration 2014-05-21 17:39:25 +02:00
DanB
201b7e7677 Removing CdrcPostMethod, default roundDecimals to 10 2014-05-21 10:13:02 +02:00
DanB
f0b2f240b1 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-05-20 13:47:25 +02:00
DanB
cb33f01695 Adding xml configuration for cdrc 2014-05-20 13:41:02 +02:00
Radu Ioan Fericean
bbe64e3e30 fix for data interval splitting (thanks DanB) 2014-05-19 23:53:22 +03:00
DanB
5aef6595a2 Fixup local tests regarding AttrGetAccount 2014-05-19 18:03:40 +02:00
DanB
1ad4068d73 Fixup CdrServer receiving nanoseconds as usage, more tests 2014-05-19 17:50:31 +02:00
DanB
d5f6df9473 Destination optional when forking Data Cdrs, adding mediation local tests for rated data charging 2014-05-19 10:46:12 +02:00
DanB
66d9377c53 DerivedChargers DurationField -> UsageField 2014-05-19 10:21:20 +02:00
DanB
79237637e1 cgrates.cfg _duration fields -> _usage one 2014-05-19 09:59:50 +02:00
DanB
e8690fc23e StoredCdr.Duration -> StoredCdr.Usage 2014-05-19 09:24:46 +02:00
DanB
aaeac3329a CgrCdr duration->usage to comply with data CDRs 2014-05-18 22:04:16 +02:00
Radu Ioan Fericean
129ca0cd93 *any date fixes 2014-05-18 20:21:29 +03:00
DanB
9bea2fa7d7 Adding datacost console command, test getDataCost 2014-05-18 18:13:59 +02:00
DanB
4d33d7cf81 DestinationRates accepting * any as destination id, adding sample data plan to prepaid1centpsec sample tariff plan 2014-05-18 14:54:34 +02:00
DanB
2acd4377e9 apier/costs.go file 2014-05-16 20:09:00 +02:00
DanB
8c29d041f1 Initial ApierV1.GetDataCosts 2014-05-16 20:08:34 +02:00
DanB
a90356d62c Fixup re-rating not using cost -1 2014-05-15 16:00:34 +02:00
DanB
040430bc3b Quick fix rated_cdrs usage format 2014-05-15 12:35:15 +02:00
DanB
d0d49481a2 ApierV1.GetCdrs method, CDR fields now exported from rated_cdrs table to reflect derived charging, duration layout, added upgrade script for mysql schema 2014-05-15 12:13:03 +02:00
DanB
d6299cd08d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-05-14 13:03:42 +02:00
DanB
65b7f19871 Adding GetActions API with tests 2014-05-14 12:46:37 +02:00
Radu Ioan Fericean
7f747c4529 test for action timing next start date 2014-05-14 12:24:24 +03:00
DanB
ec5b8711ad Fixup Tor->Category within ExportCdrsToFile API 2014-05-13 20:22:33 +02:00
DanB
dd4d9ab87e Merge branch 'master' of https://github.com/cgrates/cgrates 2014-05-13 19:21:19 +02:00
DanB
4f4f3b1108 ApierV1.GetScheduledActions with filters 2014-05-13 19:21:03 +02:00
Radu Ioan Fericean
6536465e1f check for obsolete tasks 2014-05-13 19:30:04 +03:00
DanB
c3f8521ad4 Get Aliases now from cache only 2014-05-13 11:58:50 +02:00
DanB
e157966af2 Small fixups on Alias keys 2014-05-12 19:12:42 +02:00
DanB
0011e417c2 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-05-12 19:03:14 +02:00
DanB
da6f9cad6a ApierV1 methods for add, get, remove aliases, local tests attached 2014-05-12 19:03:07 +02:00
Radu Ioan Fericean
ec66859d34 console results fix 2014-05-12 18:44:22 +03:00
Radu Ioan Fericean
0bfd104e12 add GetQueue method to scheduler 2014-05-12 16:01:47 +03:00
Radu Ioan Fericean
f8cd44f792 changed liner path 2014-05-12 13:00:40 +03:00
DanB
79d654c2a0 Aliases now with multi-tenant support, ApierV1.GetRatingSubjectAliases, ApierV1.GetAccountAliases RPC commands implementation 2014-05-11 21:48:51 +02:00
DanB
6ffdb1633f TOR in exported CDRs 2014-05-11 13:46:19 +02:00
DanB
23e2db7332 RPC MediatorV1 moved to apier package for centralized documentation 2014-05-11 13:20:25 +02:00
DanB
7bc0003fcf GetStoredCdrs taking TORs as filter 2014-05-11 13:09:53 +02:00
DanB
b54a8d7dd7 Finished adding TOR field inside CDRs 2014-05-11 11:25:29 +02:00
DanB
370fc31db8 Adding TOR support inside CDRs 2014-05-09 19:24:38 +02:00
DanB
ed3ef16c09 DBCdrs: duration -> usage to cope with the *sms and *data TORs 2014-05-08 20:08:11 +02:00
Radu Ioan Fericean
2b8b4f4791 renamed *call_duration to *voice 2014-05-08 17:09:42 +03:00
DanB
a4c556a484 Removing stale .sql file 2014-05-08 10:23:27 +02:00
DanB
e893862f26 MySQL data structures refactoring, fixups rounding methods migration 2014-05-08 10:20:46 +02:00
Radu Ioan Fericean
c879c7a4d5 more fixes 2014-05-07 20:12:48 +03:00
Radu Ioan Fericean
6d0cbfe2aa local tests fixes for rounding movement 2014-05-07 20:02:02 +03:00
Radu Ioan Fericean
456039d0a6 vagrant ansible fixes 2014-05-07 19:55:38 +03:00
Radu Ioan Fericean
7a70b50bf4 loader db updates for rounding stuf movement 2014-05-07 15:31:54 +03:00
Radu Ioan Fericean
dc7330b3c9 moved rounding params from rates to destination rates 2014-05-07 15:00:02 +03:00
Radu Ioan Fericean
928adcfbf0 no more global rounding method 2014-05-07 15:00:02 +03:00
DanB
0ef211de7e Fixups mediation derived charging 2014-05-07 13:53:44 +02:00
DanB
9512291c49 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-05-07 12:42:19 +02:00
DanB
e74a98fead Finished internal CDR handling refactoring, fixups tor->category, ratingProfile definition changes, ActionTriggers with Recurrent property 2014-05-07 12:41:57 +02:00
DanB
f466fcbb6a Proper ratingprofile order 2014-05-06 20:26:33 +02:00
DanB
f7abbacfe5 Refactoring CDRs to support RSRFields 2014-05-05 20:14:40 +02:00
Radu Ioan Fericean
f45833edeb small fix 2014-04-30 19:19:44 +03:00
Radu Ioan Fericean
7bda45fcce added max cost support 2014-04-29 15:24:54 +03:00
Radu Ioan Fericean
95a8e8a217 watch for max rate for unit costs as well 2014-04-29 14:33:28 +03:00
Radu Ioan Fericean
ede467c899 Merge branch 'lcr'
Conflicts:
	engine/calldesc.go
	engine/handler_derivedcharging.go
	mediator/mediator.go
	utils/apitpdata.go
2014-04-29 14:20:15 +03:00
Radu Ioan Fericean
fcdc0c03c7 Merge branch 'data'
Conflicts:
	engine/calldesc.go
	mediator/mediator.go
	sessionmanager/fssessionmanager.go
	utils/storedcdr.go
2014-04-29 14:13:48 +03:00
Radu Ioan Fericean
b5af601163 added max rate 2014-04-29 13:54:51 +03:00
DanB
7bd69607e4 Add rif/liner to update_external_libs.sh for packaging 2014-04-28 17:58:41 +02:00
DanB
0ebbb66dff Merge branch 'master' of https://github.com/cgrates/cgrates 2014-04-28 17:11:43 +02:00
DanB
5e114192b6 Fixup APIs DerivedCharging, adding more tests 2014-04-28 17:11:37 +02:00
Radu Ioan Fericean
e5f66a8f4b only delete if cache key exists 2014-04-28 16:37:39 +03:00
Radu Ioan Fericean
9a63438577 added lock for counters flush 2014-04-28 13:33:37 +03:00
DanB
45e798ce5d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-04-28 12:33:11 +02:00
DanB
5aebe92788 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-04-28 12:06:47 +02:00
DanB
e39187eab9 TOR -> Category in relation with DerivedCharging 2014-04-28 12:06:42 +02:00
Radu Ioan Fericean
c9493005cd more cache fixes 2014-04-28 12:45:21 +03:00
Radu Ioan Fericean
21176f7c88 cache fixes 2014-04-28 12:26:27 +03:00
Radu Ioan Fericean
24a2436f81 faster cache counter and unified caches 2014-04-28 12:12:19 +03:00
Radu Ioan Fericean
6f6f4c8c47 package updating scripts 2014-04-28 02:11:14 +03:00
DanB
6497a09ae5 RSRules chain implementation 2014-04-27 21:39:52 +02:00
DanB
69f5921a8d Fix MediatorLocal test considering derived charging 2014-04-27 14:51:05 +02:00
Radu Ioan Fericean
5bd0816596 using cgrates own branch for liner 2014-04-27 15:24:40 +03:00
DanB
e01ad95465 cdr AsStoredCdr() -> ForkCdr(), fixup ForkCdr in StoredCdr type 2014-04-27 14:17:36 +02:00
DanB
35cb7483de CgrCdr.AsStoredCdr supporting now *default as field names 2014-04-27 09:59:08 +02:00
DanB
f0095dbcb3 RawCDR interface now properly returns duration 2014-04-27 09:19:30 +02:00
Radu Ioan Fericean
a365e90c1d switched to peterh liner and fixed commands 2014-04-27 00:41:34 +03:00
Radu Ioan Fericean
509e3cde45 refactoring fixses 2014-04-26 01:52:15 +03:00
Radu Ioan Fericean
255fb4ea0e cd Tor to TOR 2014-04-26 01:50:08 +03:00
Radu Ioan Fericean
9d0f252aad lcr load and torfield to categoryfield 2014-04-26 01:45:51 +03:00
Radu Ioan Fericean
f1731cccb7 lcr loading 2014-04-26 01:33:48 +03:00
Radu Ioan Fericean
393d9091b6 merge fixes 2014-04-25 22:35:43 +03:00
Radu Ioan Fericean
1d7d0b8078 Merge branch 'data' into lcr
Conflicts:
	apier/apier.go
	cmd/cgr-loader/cgr-loader.go
	engine/calldesc.go
	engine/loader_csv.go
	engine/loader_csv_test.go
	engine/storage_interface.go
	general_tests/ddazmbl1_test.go
	utils/apitpdata.go
2014-04-25 22:10:55 +03:00
Radu Ioan Fericean
402a1fbc68 Call descriptot Type is now Tor 2014-04-25 21:50:34 +03:00
Radu Ioan Fericean
12f91b009b changed TOR in mediator 2014-04-25 21:45:05 +03:00
Radu Ioan Fericean
48de7f3c30 Merge branch 'master' into refactor
Conflicts:
	sessionmanager/fssessionmanager.go
	sessionmanager/session.go
2014-04-25 21:42:48 +03:00
Radu Ioan Fericean
a95fef496b more CallDuration changes 2014-04-25 21:22:53 +03:00
Radu Ioan Fericean
25cb5be8c0 refactor CallDuration in DurationIndex and TOR in Category 2014-04-25 21:16:21 +03:00
Radu Ioan Fericean
6526e52603 rating profile order + fix general tests 2014-04-25 20:49:44 +03:00
Radu Ioan Fericean
bc94fe34d1 millisecond for scheduler 2014-04-25 20:36:40 +03:00
DanB
330fb7b894 Fixup duration parsing, adding some tests 2014-04-25 19:35:59 +02:00
Radu Ioan Fericean
4934daa6b9 modified loader for recurrent flag 2014-04-25 20:26:41 +03:00
Radu Ioan Fericean
99faa0c112 recurrent action trigger 2014-04-25 19:23:24 +03:00
Radu Ioan Fericean
4fdf80c633 renamed *call to *call_duration 2014-04-25 19:13:14 +03:00
Radu Ioan Fericean
575bcf2434 debit zero cost calls even for no credit 2014-04-25 17:16:59 +03:00
Radu Ioan Fericean
022e3a8c38 callcost to datacost conversion method 2014-04-25 14:12:17 +03:00
Radu Ioan Fericean
c790f59b58 initial tests for data rating 2014-04-25 13:00:51 +03:00
Radu Ioan Fericean
2013c16b0e small simplification 2014-04-25 10:38:13 +03:00
Radu Ioan Fericean
4265dff012 *minutes is now *call 2014-04-25 10:37:42 +03:00
DanB
b779f09d9d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-04-25 09:32:28 +02:00
Radu Ioan Fericean
61e81ced27 small go vet fix 2014-04-25 01:20:38 +03:00
Radu Ioan Fericean
4ffc8555a1 new call descryptor type attribute 2014-04-24 22:42:14 +03:00
DanB
1953bf4233 Adding META_DEFAULT to FSevent parsing 2014-04-24 19:44:28 +02:00
Radu Ioan Fericean
42718617b9 data get cost 2014-04-24 20:01:20 +03:00
DanB
ddcd54db4b Integration of DerivedCharging within mediation 2014-04-24 18:33:18 +02:00
DanB
90ab47ade9 Session manager refactoring to work with derived charging 2014-04-24 13:39:52 +02:00
DanB
b1b3f4ab18 More derived charging infrastucture and tests 2014-04-24 10:14:02 +02:00
DanB
2a253892c5 Refactoring the code around derived charging to support integration with responder for internal requests, some of the copyright headers updated with new slogan 2014-04-23 20:48:43 +02:00
Radu Ioan Fericean
b4dbb1ca9e more time for the scheduler 2014-04-23 13:32:18 +03:00
Radu Ioan Fericean
251d504d40 fix for callcost out of bounds merge (thanks DigiDaz) 2014-04-23 13:24:41 +03:00
DanB
bba4a878f0 Refactoring configuration for DerivedCharging 2014-04-22 13:13:39 +02:00
Radu Ioan Fericean
2d042d65bf ignore empty commands (enter enter) 2014-04-21 19:12:38 +03:00
Radu Ioan Fericean
d3e5555b5c fix for rating subject shortucts 2014-04-21 12:03:15 +03:00
Radu Ioan Fericean
f8fec02ab5 fix for expiration time issue 2014-04-19 17:01:25 +03:00
Radu Ioan Fericean
83b7034b7f Merge branch 'master' into console-ng 2014-04-19 16:33:59 +03:00
Radu Ioan Fericean
efe9e19460 small fixes 2014-04-19 16:33:48 +03:00
Radu Ioan Fericean
1935d41b9d renamed commands 2014-04-19 16:06:53 +03:00
Radu Ioan Fericean
92d03eaf1a AddBalance expiration date accepting shortcuts 2014-04-19 12:37:22 +03:00
Radu Ioan Fericean
0e55acbfdb fixes 2014-04-19 12:19:44 +03:00
Radu Ioan Fericean
0122acced1 use string wrapper for string param commands 2014-04-19 11:48:05 +03:00
Radu Ioan Fericean
efc9240142 removed BalanceType from AttrGetAccount 2014-04-18 23:04:14 +03:00
Radu Ioan Fericean
bcb9582d6b more commands and help 2014-04-18 23:03:51 +03:00
DanB
94da5d9e74 Adding caching for DerivedCharging settings 2014-04-18 19:59:11 +02:00
DanB
05e4772f6d Fixup SM parse time bug discovered by DigiDaz, adding some more tests 2014-04-18 18:03:05 +02:00
DanB
4637c247fe DerivedChargers into their own files in config package 2014-04-18 16:50:15 +02:00
DanB
27e8c34fcb Fixup tests 2014-04-18 16:49:35 +02:00
Radu Ioan Fericean
6df17bf2e8 introducing command executer 2014-04-18 17:25:22 +03:00
DanB
57b4226e8a Sample DerivedChargers.csv 2014-04-18 14:43:16 +02:00
DanB
43039b063b Updating after merge 2014-04-18 14:42:52 +02:00
DanB
668352b860 Initial DerivedCharging loading 2014-04-18 14:39:59 +02:00
Radu Ioan Fericean
2da2294c0f add_balance 2014-04-17 16:59:17 +03:00
Radu Ioan Fericean
5f4580947a add_account and status 2014-04-17 16:32:20 +03:00
Radu Ioan Fericean
0192e5088f using rif's liner fork 2014-04-17 15:37:26 +03:00
Radu Ioan Fericean
4cc6d248e4 Merge branch 'master' into console-ng 2014-04-17 13:12:45 +03:00
Radu Ioan Fericean
e1c174bebb small fix 2014-04-17 13:10:43 +03:00
Radu Ioan Fericean
81b611f186 first (somehow) working version 2014-04-16 22:22:17 +03:00
DanB
d08b77fcb7 Adding TotalCost to export stats 2014-04-16 19:22:57 +02:00
DanB
341ead514e Fixup time parser to support 0 as timestamps sent by FreeSWITCH 2014-04-16 16:32:45 +02:00
DanB
0236553c87 Disabling multiprocessor support since benchmarks have shown a decrease of performance due to additional tasks management. To be reviewed in future Go versions when scheduler will be reconsidered 2014-04-16 10:57:29 +02:00
DanB
f1b639b04e Fixup responder trying to read nil in case of errors coming from above, adding some XML parsing tests 2014-04-16 10:51:27 +02:00
DanB
4e1776d6fd Discarding FS microseconds information, reverting to seconds since MySQL errors in older versions and default FS csv file uses seconds 2014-04-11 17:12:27 +02:00
DanB
5d9e7bae67 Adding more RSRTests 2014-04-10 19:12:27 +02:00
DanB
f2eade654c ExportDir as parameter to export api 2014-04-10 13:45:02 +02:00
Radu Ioan Fericean
df655e7751 handling *any destination id 2014-04-09 20:17:13 +03:00
Radu Ioan Fericean
60075616b4 first try for ga-beakon 2014-04-09 19:08:08 +03:00
DanB
b958ee38aa Merge branch 'master' of https://github.com/cgrates/cgrates 2014-04-09 17:38:35 +02:00
DanB
31138857ee Decreasing timer in case of general_tests 2014-04-09 17:37:03 +02:00
Radu Ioan Fericean
adc7c3d671 add ResetTriggeredAction api call 2014-04-09 18:27:36 +03:00
DanB
e303940d76 Adding tests on multiple balances: 0 monetary with chargeable minutes, missing monetary with chargeable minutes 2014-04-09 17:21:45 +02:00
DanB
5f65388e69 Fixup test.sh with new general_tests 2014-04-09 15:43:17 +02:00
DanB
5d29ce3d61 Changing charging_tests to general_tests to reflect more general tests inside 2014-04-09 15:40:17 +02:00
DanB
9fcf43f8b4 Corelate CgrIds between FS answer_event and FsJsonCdr 2014-04-09 12:23:56 +02:00
DanB
a7b71bae43 Fixup CgrId in FS-SM 2014-04-08 21:30:23 +02:00
Radu Ioan Fericean
c11e5ae7e7 seconds default "s" marker now optional in rates 2014-04-08 21:39:09 +03:00
Radu Ioan Fericean
d6f601e9ab use increments compression 2014-04-08 20:47:28 +03:00
Radu Ioan Fericean
fc76d891b7 first timespans compress test 2014-04-08 19:57:52 +03:00
DanB
a3fff42338 FsCgrId -> Sha1, making the CgrId even more uniquely by hashing it with setup time 2014-04-08 17:51:18 +02:00
DanB
b40c3d1653 Decreasing path in case of cdrexporter to /var/log/cgrates/cdre 2014-04-08 11:43:11 +02:00
DanB
9dcd7c0ef4 Adding OrderId to storedCdrs and using it in sql/GetStoredCdrs and exporters 2014-04-08 10:39:09 +02:00
Radu Ioan Fericean
62148b6d06 add increments compression
tests pending
2014-04-07 18:20:36 +03:00
Radu Ioan Fericean
1dca95a6d3 fix build 2014-04-07 13:54:46 +03:00
Radu Ioan Fericean
d624af2e94 Merge branch 'master' into lcr 2014-04-07 13:33:09 +03:00
Radu Ioan Fericean
6dd22fa847 zero cost fix (♥ DigiDaz) 2014-04-07 12:11:09 +03:00
Radu Ioan Fericean
2e6ee328c9 clean all balance precision, bugfix (thanks DigiDaz) 2014-04-06 23:48:55 +03:00
DanB
d5a8a2c292 Small bug fixup in action_timings possible reaching nil pointer error, adding charging_tests folder to improve testing mechanisms 2014-04-06 19:15:31 +02:00
Radu Ioan Fericean
7313e64362 rename RateSubject to RatingSubject 2014-04-06 12:58:21 +03:00
DanB
564efade3d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-04-05 13:43:58 +02:00
DanB
8fd5c112b1 Adding Account to console debit_balance 2014-04-05 13:01:30 +02:00
DanB
c940d628a6 Fixup load TpRatingPlan in storage 2014-04-04 21:09:11 +02:00
Radu Ioan Fericean
71f296d656 test for DigiDaz regression 2014-04-04 16:51:02 +03:00
DanB
517bf7964b action enable/disable_user -> enable/disable_account, cgr-tester to use test data compatible with fs_json tutorial 2014-04-03 16:36:03 +02:00
Radu Ioan Fericean
834a25ae74 Merge branch 'master' into lcr 2014-04-01 20:50:05 +03:00
Radu Ioan Fericean
a03b25cc45 cleaned unused fields 2014-04-01 20:49:34 +03:00
DanB
e5dc9a2e56 Work around in exporter api to keep config defaults 2014-04-01 16:53:04 +02:00
DanB
ac86c6b60c Fix test of costShiftDigits 2014-04-01 16:13:11 +02:00
DanB
cd29a3360b Adding costDigitsShift formatting 2014-04-01 16:08:34 +02:00
DanB
eb333fcfc0 storage/GetStoredCdrs takes a higher number of filters for exports 2014-04-01 11:56:04 +02:00
DanB
3313e7c41b Fixup local test 2014-03-31 20:34:28 +02:00
DanB
ac1e9992bf Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-31 20:29:05 +02:00
DanB
cc0534aa8b Fixup masking in the fwv exporter 2014-03-31 20:27:12 +02:00
Radu Ioan Fericean
62c56ae26b stale destination prefix cleaning 2014-03-31 21:01:52 +03:00
Radu Ioan Fericean
a8e4dad5f0 removed rateing id from callcost and loader 2014-03-31 14:18:02 +03:00
Radu Ioan Fericean
8276ea73b7 removed amount related stuff from calldesc 2014-03-31 14:06:46 +03:00
Radu Ioan Fericean
cdd93e2161 Merge branch 'master' into lcr 2014-03-31 11:44:47 +03:00
DanB
46959aaa35 Adding mask destination functionality also in csv exporter 2014-03-29 16:37:51 +01:00
DanB
c96f48435b Increasing version to avoid confusion on features support 2014-03-29 14:04:27 +01:00
DanB
65d23a1eb5 Adding MaskDestination support in cdrexporter 2014-03-29 14:01:14 +01:00
DanB
9b1ba8b6c7 Fixup packaging rules, add tab 2014-03-28 20:37:38 +01:00
DanB
cbfcd1133d Correcting rules 2014-03-28 20:35:31 +01:00
DanB
52a5fb918e Correcting packaging to reflect cdre folder 2014-03-28 20:30:02 +01:00
DanB
8325c8e2e7 Fixups cdrexporter, export path now to cdre+ export type 2014-03-28 20:13:28 +01:00
DanB
d8516d2662 Cdre-FW: filler type adds automatically padding 2014-03-28 18:38:11 +01:00
DanB
d062a98938 Concurrency fix in engine for setting history server 2014-03-27 12:17:28 +01:00
Radu Ioan Fericean
7c37399757 small cleanup 2014-03-27 09:46:51 +02:00
Radu Ioan Fericean
611743d3c5 first lcr algo 2014-03-26 11:51:03 +02:00
DanB
c8f4118780 Correcting version number 2014-03-26 10:27:35 +01:00
DanB
fded831fad Bug fixup on loading extra fields configuration, better config testing 2014-03-26 10:26:32 +01:00
Radu Ioan Fericean
e20605471e compilation fixes 2014-03-25 21:41:11 +02:00
Radu Ioan Fericean
ee6b1cd74f add lcr strategy 2014-03-25 21:38:18 +02:00
Radu Ioan Fericean
d95d842ede Merge branch 'master' into lcr 2014-03-25 18:17:47 +02:00
Radu Ioan Fericean
8987b11cbd added lcr storage methods 2014-03-25 18:01:49 +02:00
Radu Ioan Fericean
26b9590fa0 Merge branch 'master' into lcr 2014-03-25 09:59:37 +02:00
Radu Ioan Fericean
4e42c69df6 merged master 2014-03-07 12:17:45 +02:00
Radu Ioan Fericean
21b80c992b started LCR work 2014-03-07 12:15:28 +02:00
Radu Ioan Fericean
56faf676dd smal fix for csv header 2014-03-07 12:12:31 +02:00
298 changed files with 21514 additions and 7465 deletions

View File

@@ -2,6 +2,8 @@ language: go
go:
- 1.2
- 1.3
- tip
script: $TRAVIS_BUILD_DIR/test.sh

View File

@@ -1,6 +1,6 @@
## Rating system for Telecom & ISP environments ##
## Real-time Charging System for Telecom & ISP environments ##
[![Build Status](https://drone.io/github.com/cgrates/cgrates/status.png)](https://drone.io/github.com/cgrates/cgrates/latest) [![Build Status](https://secure.travis-ci.org/cgrates/cgrates.png)](http://travis-ci.org/cgrates/cgrates)
[![Build Status](https://secure.travis-ci.org/cgrates/cgrates.png)](http://travis-ci.org/cgrates/cgrates)
### Features ###
+ Rates for prepaid and for postpaid
@@ -24,4 +24,6 @@ PDF, Epub, Manpage http://readthedocs.org/projects/cgrates/downloads/
API reference [godoc](http://godoc.org/github.com/cgrates/cgrates/apier)
Also check irc.freenode.net#cgrates and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support.
Also check [irc.freenode.net #cgrates](irc://irc.freenode.net:6667/cgrates) ([Webchat](http://webchat.freenode.net?randomnick=1&channels=%23cgrates)) and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support.
[![Analytics](https://ga-beacon.appspot.com/UA-10073547-11/cgrates/readme)](https://github.com/igrigorik/ga-beacon)

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -51,7 +51,7 @@ func (self *ApierV1) GetAccountActionPlan(attrs AttrAcntAction, reply *[]*Accoun
}
for _, ats := range allATs {
for _, at := range ats {
if utils.IsSliceMember(at.AccountIds, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)) {
if utils.IsSliceMember(at.AccountIds, utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction)) {
accountATs = append(accountATs, &AccountActionTiming{Uuid: at.Uuid, ActionPlanId: at.Id, ActionsId: at.ActionsId, NextExecTime: at.GetNextStartTime(time.Now())})
}
}
@@ -86,7 +86,7 @@ func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) e
} else if len(ats) == 0 {
return 0, errors.New(utils.ERR_NOT_FOUND)
}
ats = engine.RemActionTiming(ats, attrs.ActionTimingId, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction))
ats = engine.RemActionTiming(ats, attrs.ActionTimingId, utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction))
if err := self.AccountDb.SetActionTimings(attrs.ActionPlanId, ats); err != nil {
return 0, err
}
@@ -108,7 +108,7 @@ func (self *ApierV1) GetAccountActionTriggers(attrs AttrAcntAction, reply *engin
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if balance, err := self.AccountDb.GetAccount(utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)); err != nil {
if balance, err := self.AccountDb.GetAccount(utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction)); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = balance.ActionTriggers
@@ -128,7 +128,7 @@ func (self *ApierV1) RemAccountActionTriggers(attrs AttrRemAcntActionTriggers, r
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
balanceId := utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)
balanceId := utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction)
_, err := engine.AccLock.Guard(balanceId, func() (float64, error) {
ub, err := self.AccountDb.GetAccount(balanceId)
if err != nil {
@@ -169,7 +169,7 @@ func (self *ApierV1) SetAccount(attr AttrSetAccount, reply *string) error {
if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Direction", "Account"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
balanceId := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction)
balanceId := utils.AccountKey(attr.Tenant, attr.Account, attr.Direction)
var ub *engine.Account
var ats engine.ActionPlan
_, err := engine.AccLock.Guard(balanceId, func() (float64, error) {

137
apier/aliases.go Normal file
View File

@@ -0,0 +1,137 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
type AttrAddRatingSubjectAliases struct {
Tenant, Subject string
Aliases []string
}
type AttrAddAccountAliases struct {
Tenant, Account string
Aliases []string
}
// Retrieve aliases configured for a rating profile subject
func (self *ApierV1) AddRatingSubjectAliases(attrs AttrAddRatingSubjectAliases, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Subject", "Aliases"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
aliasesChanged := []string{}
for _, alias := range attrs.Aliases {
if err := self.RatingDb.SetRpAlias(utils.RatingSubjectAliasKey(attrs.Tenant, alias), attrs.Subject); err != nil {
return fmt.Errorf("%s:%s:%s", utils.ERR_SERVER_ERROR, alias, err.Error())
}
aliasesChanged = append(aliasesChanged, engine.RP_ALIAS_PREFIX+utils.RatingSubjectAliasKey(attrs.Tenant, alias))
}
didNotChange := []string{}
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, didNotChange, aliasesChanged, didNotChange); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
return nil
}
// Retrieve aliases configured for a rating profile subject
func (self *ApierV1) GetRatingSubjectAliases(attrs engine.TenantRatingSubject, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Subject"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if aliases, err := self.RatingDb.GetRPAliases(attrs.Tenant, attrs.Subject, false); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(aliases) == 0 { // Need it since otherwise we get some unexpected errrors in the client
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = aliases
}
return nil
}
// Retrieve aliases configured for a rating profile subject
func (self *ApierV1) RemRatingSubjectAliases(tenantRatingSubject engine.TenantRatingSubject, reply *string) error {
if missing := utils.MissingStructFields(&tenantRatingSubject, []string{"Tenant", "Subject"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.RatingDb.RemoveRpAliases([]*engine.TenantRatingSubject{&tenantRatingSubject}); err != nil {
return fmt.Errorf("%s:% s", utils.ERR_SERVER_ERROR, err.Error())
}
didNotChange := []string{}
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, didNotChange, nil, didNotChange); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
return nil
}
func (self *ApierV1) AddAccountAliases(attrs AttrAddAccountAliases, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Aliases"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
aliasesChanged := []string{}
for _, alias := range attrs.Aliases {
if err := self.AccountDb.SetAccAlias(utils.AccountAliasKey(attrs.Tenant, alias), attrs.Account); err != nil {
return fmt.Errorf("%s:%s:%s", utils.ERR_SERVER_ERROR, alias, err.Error())
}
aliasesChanged = append(aliasesChanged, engine.ACC_ALIAS_PREFIX+utils.AccountAliasKey(attrs.Tenant, alias))
}
didNotChange := []string{}
if err := self.AccountDb.CacheAccounting(didNotChange, didNotChange, aliasesChanged, didNotChange); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
return nil
}
// Retrieve aliases configured for an account
func (self *ApierV1) GetAccountAliases(attrs engine.TenantAccount, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if aliases, err := self.AccountDb.GetAccountAliases(attrs.Tenant, attrs.Account, false); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(aliases) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = aliases
}
return nil
}
// Retrieve aliases configured for a rating profile subject
func (self *ApierV1) RemAccountAliases(tenantAccount engine.TenantAccount, reply *string) error {
if missing := utils.MissingStructFields(&tenantAccount, []string{"Tenant", "Account"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.AccountDb.RemoveAccAliases([]*engine.TenantAccount{&tenantAccount}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
didNotChange := []string{}
if err := self.AccountDb.CacheAccounting(didNotChange, didNotChange, nil, didNotChange); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
return nil
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package apier
import (
"encoding/json"
"errors"
"fmt"
"path"
"time"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/config"
@@ -36,13 +36,15 @@ const (
)
type ApierV1 struct {
StorDb engine.LoadStorage
RatingDb engine.RatingStorage
AccountDb engine.AccountingStorage
CdrDb engine.CdrStorage
LogDb engine.LogStorage
Sched *scheduler.Scheduler
Config *config.CGRConfig
StorDb engine.LoadStorage
RatingDb engine.RatingStorage
AccountDb engine.AccountingStorage
CdrDb engine.CdrStorage
LogDb engine.LogStorage
Sched *scheduler.Scheduler
Config *config.CGRConfig
Responder *engine.Responder
CdrStatsSrv *engine.Stats
}
func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) error {
@@ -54,6 +56,12 @@ func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) err
return nil
}
type AttrSetDestination struct { //ToDo
Id string
Prefixes []string
Overwrite bool
}
func (self *ApierV1) GetRatingPlan(rplnId string, reply *engine.RatingPlan) error {
if rpln, err := self.RatingDb.GetRatingPlan(rplnId, false); err != nil {
return errors.New(utils.ERR_NOT_FOUND)
@@ -63,15 +71,8 @@ func (self *ApierV1) GetRatingPlan(rplnId string, reply *engine.RatingPlan) erro
return nil
}
type AttrGetAccount struct {
Tenant string
Account string
BalanceType string
Direction string
}
// Get balance
func (self *ApierV1) GetAccount(attr *AttrGetAccount, reply *engine.Account) error {
func (self *ApierV1) GetAccount(attr *utils.AttrGetAccount, reply *engine.Account) error {
tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account)
userBalance, err := self.AccountDb.GetAccount(tag)
if err != nil {
@@ -83,19 +84,25 @@ func (self *ApierV1) GetAccount(attr *AttrGetAccount, reply *engine.Account) err
}
type AttrAddBalance struct {
Tenant string
Account string
BalanceType string
Direction string
Value float64
ExpirationDate time.Time
RatingSubject string
DestinationId string
Weight float64
Overwrite bool // When true it will reset if the balance is already there
Tenant string
Account string
BalanceType string
Direction string
Value float64
ExpiryTime string
RatingSubject string
DestinationId string
Weight float64
SharedGroup string
Overwrite bool // When true it will reset if the balance is already there
}
func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
expTime, err := utils.ParseDate(attr.ExpiryTime)
if err != nil {
*reply = err.Error()
return err
}
tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account)
if _, err := self.AccountDb.GetAccount(tag); err != nil {
// create user balance if not exists
@@ -114,9 +121,12 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
if attr.Direction == "" {
attr.Direction = engine.OUTBOUND
}
aType := engine.TOPUP
aType := engine.DEBIT
// reverse the sign as it is a debit
attr.Value = -attr.Value
if attr.Overwrite {
aType = engine.TOPUP_RESET
aType = engine.DEBIT_RESET
}
at.SetActions(engine.Actions{
&engine.Action{
@@ -125,10 +135,11 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
Direction: attr.Direction,
Balance: &engine.Balance{
Value: attr.Value,
ExpirationDate: attr.ExpirationDate,
RateSubject: attr.RatingSubject,
ExpirationDate: expTime,
RatingSubject: attr.RatingSubject,
DestinationId: attr.DestinationId,
Weight: attr.Weight,
SharedGroup: attr.SharedGroup,
},
},
})
@@ -180,7 +191,7 @@ func (self *ApierV1) LoadRatingPlan(attrs AttrLoadRatingPlan, reply *string) err
}
//Automatic cache of the newly inserted rating plan
didNotChange := []string{}
if err := self.RatingDb.CacheRating(nil, nil, didNotChange, didNotChange); err != nil {
if err := self.RatingDb.CacheRating(nil, nil, didNotChange, didNotChange, didNotChange); err != nil {
return err
}
*reply = OK
@@ -198,7 +209,7 @@ func (self *ApierV1) LoadRatingProfile(attrs utils.TPRatingProfile, reply *strin
}
//Automatic cache of the newly inserted rating profile
didNotChange := []string{}
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + attrs.KeyId()}, didNotChange); err != nil {
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + attrs.KeyId()}, didNotChange, didNotChange); err != nil {
return err
}
*reply = OK
@@ -207,7 +218,7 @@ func (self *ApierV1) LoadRatingProfile(attrs utils.TPRatingProfile, reply *strin
type AttrSetRatingProfile struct {
Tenant string // Tenant's Id
TOR string // TypeOfRecord
Category string // TypeOfRecord
Direction string // Traffic direction, OUT is the only one supported for now
Subject string // Rating subject, usually the same as account
Overwrite bool // Overwrite if exists
@@ -224,7 +235,7 @@ func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string)
return fmt.Errorf("%s:RatingPlanActivation:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
}
tpRpf := utils.TPRatingProfile{Tenant: attrs.Tenant, TOR: attrs.TOR, Direction: attrs.Direction, Subject: attrs.Subject}
tpRpf := utils.TPRatingProfile{Tenant: attrs.Tenant, Category: attrs.Category, Direction: attrs.Direction, Subject: attrs.Subject}
keyId := tpRpf.KeyId()
if !attrs.Overwrite {
if exists, err := self.RatingDb.HasData(engine.RATING_PROFILE_PREFIX, keyId); err != nil {
@@ -245,14 +256,14 @@ func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string)
return fmt.Errorf(fmt.Sprintf("%s:RatingPlanId:%s", utils.ERR_NOT_FOUND, ra.RatingPlanId))
}
rpfl.RatingPlanActivations[idx] = &engine.RatingPlanActivation{ActivationTime: at, RatingPlanId: ra.RatingPlanId,
FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.TOR, ra.FallbackSubjects)}
FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, ra.FallbackSubjects)}
}
if err := self.RatingDb.SetRatingProfile(rpfl); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
//Automatic cache of the newly inserted rating profile
didNotChange := []string{}
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + keyId}, didNotChange); err != nil {
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + keyId}, didNotChange, didNotChange); err != nil {
return err
}
*reply = OK
@@ -300,7 +311,8 @@ func (self *ApierV1) SetActions(attrs AttrSetActions, reply *string) error {
Value: apiAct.Units,
Weight: apiAct.BalanceWeight,
DestinationId: apiAct.DestinationId,
RateSubject: apiAct.RatingSubject,
RatingSubject: apiAct.RatingSubject,
SharedGroup: apiAct.SharedGroup,
},
}
storeActions[idx] = a
@@ -308,10 +320,43 @@ func (self *ApierV1) SetActions(attrs AttrSetActions, reply *string) error {
if err := self.AccountDb.SetActions(attrs.ActionsId, storeActions); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
didNotChange := []string{}
self.AccountDb.CacheAccounting(nil, didNotChange, didNotChange, didNotChange)
*reply = OK
return nil
}
// Retrieves actions attached to specific ActionsId within cache
func (self *ApierV1) GetActions(actsId string, reply *[]*utils.TPAction) error {
if len(actsId) == 0 {
return fmt.Errorf("%s:ActionsId", utils.ERR_MANDATORY_IE_MISSING, actsId)
}
acts := make([]*utils.TPAction, 0)
engActs, err := self.AccountDb.GetActions(actsId, false)
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
for _, engAct := range engActs {
act := &utils.TPAction{Identifier: engAct.ActionType,
BalanceType: engAct.BalanceType,
Direction: engAct.Direction,
ExpiryTime: engAct.ExpirationString,
ExtraParameters: engAct.ExtraParameters,
Weight: engAct.Weight,
}
if engAct.Balance != nil {
act.Units = engAct.Balance.Value
act.DestinationId = engAct.Balance.DestinationId
act.RatingSubject = engAct.Balance.RatingSubject
act.SharedGroup = engAct.Balance.SharedGroup
act.BalanceWeight = engAct.Balance.Weight
}
acts = append(acts, act)
}
*reply = acts
return nil
}
type AttrSetActionPlan struct {
Id string // Profile id
ActionPlan []*ApiActionTiming // Set of actions this Actions profile will perform
@@ -383,36 +428,45 @@ func (self *ApierV1) SetActionPlan(attrs AttrSetActionPlan, reply *string) error
}
type AttrAddActionTrigger struct {
Tenant string
Account string
Direction string
BalanceType string
ThresholdType string
ThresholdValue float64
DestinationId string
Weight float64
ActionsId string
Tenant string
Account string
Direction string
BalanceType string
ThresholdType string
ThresholdValue float64
DestinationId string
BalanceRatingSubject string //ToDo
BalanceWeight float64
BalanceExpiryTime string
BalanceSharedGroup string //ToDo
Weight float64
ActionsId string
}
func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error {
if attr.Direction == "" {
attr.Direction = engine.OUTBOUND
}
balExpiryTime, err := utils.ParseTimeDetectLayout(attr.BalanceExpiryTime)
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
at := &engine.ActionTrigger{
Id: utils.GenUUID(),
BalanceType: attr.BalanceType,
Direction: attr.Direction,
ThresholdType: attr.ThresholdType,
ThresholdValue: attr.ThresholdValue,
DestinationId: attr.DestinationId,
Weight: attr.Weight,
ActionsId: attr.ActionsId,
Executed: false,
Id: utils.GenUUID(),
BalanceType: attr.BalanceType,
Direction: attr.Direction,
ThresholdType: attr.ThresholdType,
ThresholdValue: attr.ThresholdValue,
DestinationId: attr.DestinationId,
BalanceWeight: attr.BalanceWeight,
BalanceExpirationDate: balExpiryTime,
Weight: attr.Weight,
ActionsId: attr.ActionsId,
Executed: false,
}
tag := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction)
_, err := engine.AccLock.Guard(tag, func() (float64, error) {
tag := utils.AccountKey(attr.Tenant, attr.Account, attr.Direction)
_, err = engine.AccLock.Guard(tag, func() (float64, error) {
userBalance, err := self.AccountDb.GetAccount(tag)
if err != nil {
return 0, err
@@ -433,6 +487,69 @@ func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string
return nil
}
type AttrResetTriggeredAction struct {
Tenant string
Account string
Direction string
BalanceType string
ThresholdType string
ThresholdValue float64
DestinationId string
BalanceWeight float64
BalanceRatingSubject string
BalanceSharedGroup string
}
func (self *ApierV1) ResetTriggeredActions(attr AttrResetTriggeredAction, reply *string) error {
if attr.Direction == "" {
attr.Direction = engine.OUTBOUND
}
extraParameters, err := json.Marshal(struct {
ThresholdType string
ThresholdValue float64
DestinationId string
BalanceWeight float64
BalanceRatingSubject string
BalanceSharedGroup string
}{
attr.ThresholdType,
attr.ThresholdValue,
attr.DestinationId,
attr.BalanceWeight,
attr.BalanceRatingSubject,
attr.BalanceSharedGroup,
})
if err != nil {
*reply = err.Error()
return err
}
a := &engine.Action{
BalanceType: attr.BalanceType,
Direction: attr.Direction,
ExtraParameters: string(extraParameters),
}
accID := utils.AccountKey(attr.Tenant, attr.Account, attr.Direction)
_, err = engine.AccLock.Guard(accID, func() (float64, error) {
acc, err := self.AccountDb.GetAccount(accID)
if err != nil {
return 0, err
}
acc.ResetActionTriggers(a)
if err = self.AccountDb.SetAccount(acc); err != nil {
return 0, err
}
return 0, nil
})
if err != nil {
*reply = err.Error()
return err
}
*reply = OK
return nil
}
// Process dependencies and load a specific AccountActions profile from storDb into dataDb.
func (self *ApierV1) LoadAccountActions(attrs utils.TPAccountActions, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Account", "Direction"}); len(missing) != 0 {
@@ -450,7 +567,7 @@ func (self *ApierV1) LoadAccountActions(attrs utils.TPAccountActions, reply *str
}
// ToDo: Get the action keys loaded by dbReader so we reload only these in cache
// Need to do it before scheduler otherwise actions to run will be unknown
if err := self.AccountDb.CacheAccounting(nil, nil, nil); err != nil {
if err := self.AccountDb.CacheAccounting(nil, nil, nil, []string{}); err != nil {
return err
}
if self.Sched != nil {
@@ -473,7 +590,7 @@ func (self *ApierV1) ReloadScheduler(input string, reply *string) error {
}
func (self *ApierV1) ReloadCache(attrs utils.ApiReloadCache, reply *string) error {
var dstKeys, rpKeys, rpfKeys, actKeys, shgKeys, rpAlsKeys, accAlsKeys []string
var dstKeys, rpKeys, rpfKeys, actKeys, shgKeys, rpAlsKeys, accAlsKeys, lcrKeys, dcsKeys []string
if len(attrs.DestinationIds) > 0 {
dstKeys = make([]string, len(attrs.DestinationIds))
for idx, dId := range attrs.DestinationIds {
@@ -516,10 +633,23 @@ func (self *ApierV1) ReloadCache(attrs utils.ApiReloadCache, reply *string) erro
accAlsKeys[idx] = engine.ACC_ALIAS_PREFIX + alias
}
}
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys); err != nil {
if len(attrs.LCRIds) > 0 {
lcrKeys = make([]string, len(attrs.LCRIds))
for idx, lcrId := range attrs.LCRIds {
lcrKeys[idx] = engine.LCR_PREFIX + lcrId
}
}
if len(attrs.DerivedChargers) > 0 {
dcsKeys = make([]string, len(attrs.DerivedChargers))
for idx, dc := range attrs.DerivedChargers {
dcsKeys[idx] = engine.DERIVEDCHARGERS_PREFIX + dc
}
}
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys, lcrKeys); err != nil {
return err
}
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys); err != nil {
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys, dcsKeys); err != nil {
return err
}
*reply = "OK"
@@ -535,6 +665,7 @@ func (self *ApierV1) GetCacheStats(attrs utils.AttrCacheStats, reply *utils.Cach
cs.SharedGroups = cache2go.CountEntries(engine.SHARED_GROUP_PREFIX)
cs.RatingAliases = cache2go.CountEntries(engine.RP_ALIAS_PREFIX)
cs.AccountAliases = cache2go.CountEntries(engine.ACC_ALIAS_PREFIX)
cs.DerivedChargers = cache2go.CountEntries(engine.DERIVEDCHARGERS_PREFIX)
*reply = *cs
return nil
}
@@ -583,10 +714,13 @@ func (self *ApierV1) LoadTariffPlanFromFolder(attrs utils.AttrLoadTpFromFolder,
path.Join(attrs.FolderPath, utils.RATING_PLANS_CSV),
path.Join(attrs.FolderPath, utils.RATING_PROFILES_CSV),
path.Join(attrs.FolderPath, utils.SHARED_GROUPS_CSV),
path.Join(attrs.FolderPath, utils.LCRS_CSV),
path.Join(attrs.FolderPath, utils.ACTIONS_CSV),
path.Join(attrs.FolderPath, utils.ACTION_PLANS_CSV),
path.Join(attrs.FolderPath, utils.ACTION_TRIGGERS_CSV),
path.Join(attrs.FolderPath, utils.ACCOUNT_ACTIONS_CSV))
path.Join(attrs.FolderPath, utils.ACCOUNT_ACTIONS_CSV),
path.Join(attrs.FolderPath, utils.DERIVED_CHARGERS_CSV),
path.Join(attrs.FolderPath, utils.CDR_STATS_CSV))
if err := loader.LoadAll(); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
@@ -633,16 +767,32 @@ func (self *ApierV1) LoadTariffPlanFromFolder(attrs utils.AttrLoadTpFromFolder,
for idx, alias := range accAliases {
accAlsKeys[idx] = engine.ACC_ALIAS_PREFIX + alias
}
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys); err != nil {
lcrIds, _ := loader.GetLoadedIds(engine.LCR_PREFIX)
lcrKeys := make([]string, len(lcrIds))
for idx, lcrId := range lcrIds {
lcrKeys[idx] = engine.LCR_PREFIX + lcrId
}
dcs, _ := loader.GetLoadedIds(engine.DERIVEDCHARGERS_PREFIX)
dcsKeys := make([]string, len(dcs))
for idx, dc := range dcs {
dcsKeys[idx] = engine.DERIVEDCHARGERS_PREFIX + dc
}
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys, lcrKeys); err != nil {
return err
}
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys); err != nil {
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys, dcsKeys); err != nil {
return err
}
if self.Sched != nil {
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
}
cstKeys, _ := loader.GetLoadedIds(engine.CDR_STATS_PREFIX)
if len(cstKeys) != 0 && self.CdrStatsSrv != nil {
if err := self.CdrStatsSrv.ReloadQueues(cstKeys, nil); err != nil {
return err
}
}
*reply = "OK"
return nil
}

View File

@@ -1,14 +1,14 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
@@ -30,7 +30,7 @@ import (
"os/exec"
"path"
"reflect"
"strings"
"sort"
"testing"
"time"
@@ -66,17 +66,20 @@ var waitRater = flag.Int("wait_rater", 500, "Number of miliseconds to wait for r
func init() {
cfgPath = path.Join(*dataDir, "conf", "samples", "apier_local_test.cfg")
cfg, _ = config.NewCGRConfig(&cfgPath)
cfg, _ = config.NewCGRConfigFromFile(&cfgPath)
}
func TestCreateDirs(t *testing.T) {
if !*testLocal {
return
}
for _, pathDir := range []string{cfg.CdreDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
if err := os.RemoveAll(pathDir); err != nil {
t.Fatal("Error removing folder: ", pathDir, err)
}
if err := os.MkdirAll(pathDir, 0755); err != nil {
t.Fatal("Error creating folder: ", pathDir, err)
}
}
}
@@ -94,8 +97,7 @@ func TestCreateTables(t *testing.T) {
} else {
mysql = d.(*engine.MySQLStorage)
}
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL,
engine.CREATE_TARIFFPLAN_TABLES_SQL} {
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
t.Fatal("Error on mysql creation: ", err.Error())
return // No point in going further
@@ -286,7 +288,7 @@ func TestApierTPRate(t *testing.T) {
}
reply := ""
rt := &utils.TPRate{TPid: engine.TEST_SQL, RateId: "RT_FS_USERS", RateSlots: []*utils.RateSlot{
&utils.RateSlot{ConnectFee: 0, Rate: 0, RateUnit: "60s", RateIncrement: "60s", GroupIntervalStart: "0s", RoundingMethod: "*up", RoundingDecimals: 0},
&utils.RateSlot{ConnectFee: 0, Rate: 0, RateUnit: "60s", RateIncrement: "60s", GroupIntervalStart: "0s"},
}}
rt2 := new(utils.TPRate)
*rt2 = *rt
@@ -340,10 +342,10 @@ func TestApierTPDestinationRate(t *testing.T) {
}
reply := ""
dr := &utils.TPDestinationRate{TPid: engine.TEST_SQL, DestinationRateId: "DR_FREESWITCH_USERS", DestinationRates: []*utils.DestinationRate{
&utils.DestinationRate{DestinationId: "FS_USERS", RateId: "RT_FS_USERS"},
&utils.DestinationRate{DestinationId: "FS_USERS", RateId: "RT_FS_USERS", RoundingMethod: "*up", RoundingDecimals: 2},
}}
drDe := &utils.TPDestinationRate{TPid: engine.TEST_SQL, DestinationRateId: "DR_FREESWITCH_USERS", DestinationRates: []*utils.DestinationRate{
&utils.DestinationRate{DestinationId: "GERMANY_MOBILE", RateId: "RT_FS_USERS"},
&utils.DestinationRate{DestinationId: "GERMANY_MOBILE", RateId: "RT_FS_USERS", RoundingMethod: "*up", RoundingDecimals: 2},
}}
dr2 := new(utils.TPDestinationRate)
*dr2 = *dr
@@ -450,7 +452,7 @@ func TestApierTPRatingProfile(t *testing.T) {
return
}
reply := ""
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", TOR: "call", Direction: "*out", Subject: "*any",
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", Category: "call", Direction: "*out", Subject: "*any",
RatingPlanActivations: []*utils.TPRatingActivation{
&utils.TPRatingActivation{ActivationTime: "2012-01-01T00:00:00Z", RatingPlanId: "RETAIL1", FallbackSubjects: ""},
}}
@@ -473,7 +475,7 @@ func TestApierTPRatingProfile(t *testing.T) {
// Check missing params
if err := rater.Call("ApierV1.SetTPRatingProfile", new(utils.TPRatingProfile), &reply); err == nil {
t.Error("Calling ApierV1.SetTPRatingProfile, expected error, received: ", reply)
} else if err.Error() != "MANDATORY_IE_MISSING:[TPid LoadId Tenant TOR Direction Subject RatingPlanActivations]" {
} else if err.Error() != "MANDATORY_IE_MISSING:[TPid LoadId Tenant Category Direction Subject RatingPlanActivations]" {
t.Error("Calling ApierV1.SetTPRatingProfile got unexpected error: ", err.Error())
}
// Test get
@@ -743,7 +745,7 @@ func TestApierSetRatingProfile(t *testing.T) {
}
reply := ""
rpa := &utils.TPRatingActivation{ActivationTime: "2012-01-01T00:00:00Z", RatingPlanId: "RETAIL1", FallbackSubjects: "dan2"}
rpf := &AttrSetRatingProfile{Tenant: "cgrates.org", TOR: "call", Direction: "*out", Subject: "dan", RatingPlanActivations: []*utils.TPRatingActivation{rpa}}
rpf := &AttrSetRatingProfile{Tenant: "cgrates.org", Category: "call", Direction: "*out", Subject: "dan", RatingPlanActivations: []*utils.TPRatingActivation{rpa}}
if err := rater.Call("ApierV1.SetRatingProfile", rpf, &reply); err != nil {
t.Error("Got error on ApierV1.SetRatingProfile: ", err.Error())
} else if reply != "OK" {
@@ -759,15 +761,15 @@ func TestApierSetRatingProfile(t *testing.T) {
tStart, _ := utils.ParseDate("2013-08-07T17:30:00Z")
tEnd, _ := utils.ParseDate("2013-08-07T17:31:30Z")
cd := engine.CallDescriptor{
Direction: "*out",
TOR: "call",
Tenant: "cgrates.org",
Subject: "dan",
Account: "dan",
Destination: "+4917621621391",
CallDuration: 90,
TimeStart: tStart,
TimeEnd: tEnd,
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "dan",
Account: "dan",
Destination: "+4917621621391",
DurationIndex: 90,
TimeStart: tStart,
TimeEnd: tEnd,
}
var cc engine.CallCost
// Simple test that command is executed without errors
@@ -784,7 +786,7 @@ func TestApierLoadRatingProfile(t *testing.T) {
return
}
reply := ""
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", TOR: "call", Direction: "*out", Subject: "*any"}
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", Category: "call", Direction: "*out", Subject: "*any"}
if err := rater.Call("ApierV1.LoadRatingProfile", rpf, &reply); err != nil {
t.Error("Got error on ApierV1.LoadRatingProfile: ", err.Error())
} else if reply != "OK" {
@@ -891,7 +893,6 @@ func TestApierGetRatingPlan(t *testing.T) {
}
reply := new(engine.RatingPlan)
rplnId := "RETAIL1"
//{"Id":"RETAIL1","Timings":{"96c78ff5":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""}},"Ratings":{"e41ffcf2":{"ConnectFee":0,"Rates":[{"GroupIntervalStart":0,"Value":0,"RateIncrement":60000000000,"RateUnit":60000000000}],"RoundingMethod":"*up","RoundingDecimals":0}},"DestinationRates":{"FS_USERS":[{"Timing":"96c78ff5","Rating":"e41ffcf2","Weight":10}],"GERMANY_MOBILE":[{"Timing":"96c78ff5","Rating":"e41ffcf2","Weight":10}]}
if err := rater.Call("ApierV1.GetRatingPlan", rplnId, reply); err != nil {
t.Error("Got error on ApierV1.GetRatingPlan: ", err.Error())
}
@@ -902,21 +903,13 @@ func TestApierGetRatingPlan(t *testing.T) {
if len(reply.Timings) != 1 || len(reply.Ratings) != 1 {
t.Error("Unexpected number of items received")
}
/*
riTiming := &engine.RITiming{StartTime: "00:00:00"}
for _, tm := range reply.Timings { // We only get one loop
if !reflect.DeepEqual(tm, riTiming) {
t.Errorf("Unexpected timings value: %v, expecting: %v", tm, riTiming)
}
}
*/
riRate := &engine.RIRate{Id: "RT_FS_USERS", ConnectFee: 0, RoundingMethod: "*up", RoundingDecimals: 0, Rates: []*engine.Rate{
riRate := &engine.RIRate{ConnectFee: 0, RoundingMethod: "*up", RoundingDecimals: 2, Rates: []*engine.Rate{
&engine.Rate{GroupIntervalStart: 0, Value: 0, RateIncrement: time.Duration(60) * time.Second, RateUnit: time.Duration(60) * time.Second},
}}
for _, rating := range reply.Ratings {
riRateJsson, _ := json.Marshal(rating)
riRateJson, _ := json.Marshal(rating)
if !reflect.DeepEqual(rating, riRate) {
t.Errorf("Unexpected riRate received: %s", riRateJsson)
t.Errorf("Unexpected riRate received: %s", riRateJson)
// {"Id":"RT_FS_USERS","ConnectFee":0,"Rates":[{"GroupIntervalStart":0,"Value":0,"RateIncrement":60000000000,"RateUnit":60000000000}],"RoundingMethod":"*up","RoundingDecimals":0}
}
}
@@ -994,7 +987,7 @@ func TestApierExecuteAction(t *testing.T) {
}
}
func TestApierSetActions(t *testing.T) {
func TestLocalApierSetActions(t *testing.T) {
if !*testLocal {
return
}
@@ -1012,6 +1005,21 @@ func TestApierSetActions(t *testing.T) {
}
}
func TestLocalApierGetActions(t *testing.T) {
if !*testLocal {
return
}
expectActs := []*utils.TPAction{
&utils.TPAction{Identifier: engine.TOPUP_RESET, BalanceType: engine.CREDIT, Direction: engine.OUTBOUND, Units: 75.0, ExpiryTime: engine.UNLIMITED, Weight: 20.0}}
var reply []*utils.TPAction
if err := rater.Call("ApierV1.GetActions", "ACTS_1", &reply); err != nil {
t.Error("Got error on ApierV1.GetActions: ", err.Error())
} else if !reflect.DeepEqual(expectActs, reply) {
t.Errorf("Expected: %v, received: %v", expectActs, reply)
}
}
func TestApierSetActionPlan(t *testing.T) {
if !*testLocal {
return
@@ -1162,35 +1170,35 @@ func TestApierGetAccount(t *testing.T) {
return
}
var reply *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 11.5 { // We expect 11.5 since we have added in the previous test 1.5
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 11.5 { // We expect 11.5 since we have added in the previous test 1.5
t.Errorf("Calling ApierV1.GetBalance expected: 11.5, received: %f", reply)
}
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan", BalanceType: "*monetary", Direction: "*out"}
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 1.5 {
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 1.5 {
t.Errorf("Calling ApierV1.GetAccount expected: 1.5, received: %f", reply)
}
// The one we have topped up though executeAction
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan2", BalanceType: "*monetary", Direction: "*out"}
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan2", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10 {
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10 {
t.Errorf("Calling ApierV1.GetAccount expected: 10, received: %f", reply)
}
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan3", BalanceType: "*monetary", Direction: "*out"}
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan3", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 3.6 {
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 3.6 {
t.Errorf("Calling ApierV1.GetAccount expected: 3.6, received: %f", reply)
}
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan6", BalanceType: "*monetary", Direction: "*out"}
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan6", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 1 {
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 1 {
t.Errorf("Calling ApierV1.GetAccount expected: 1, received: %f", reply)
}
}
@@ -1252,7 +1260,29 @@ func TestApierLoadTariffPlanFromFolder(t *testing.T) {
} else if reply != "OK" {
t.Error("Calling ApierV1.LoadTariffPlanFromFolder got reply: ", reply)
}
time.Sleep(100 * time.Millisecond) // Give time for scheduler to execute topups
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
}
func TestResetDataAfterLoadFromFolder(t *testing.T) {
if !*testLocal {
return
}
reply := ""
arc := new(utils.ApiReloadCache)
// Simple test that command is executed without errors
if err := rater.Call("ApierV1.ReloadCache", arc, &reply); err != nil {
t.Error("Got error on ApierV1.ReloadCache: ", err.Error())
} else if reply != "OK" {
t.Error("Calling ApierV1.ReloadCache got reply: ", reply)
}
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 3, Actions: 5, DerivedChargers: 2}
var args utils.AttrCacheStats
if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
} else if !reflect.DeepEqual(rcvStats, expectedStats) {
t.Errorf("Calling ApierV1.GetCacheStats received: %v, expected: %v", rcvStats, expectedStats)
}
}
// Make sure balance was topped-up
@@ -1262,11 +1292,11 @@ func TestApierGetAccountAfterLoad(t *testing.T) {
return
}
var reply *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 11 {
t.Errorf("Calling ApierV1.GetBalance expected: 11, received: %f", reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 11 {
t.Errorf("Calling ApierV1.GetBalance expected: 11, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
}
@@ -1278,15 +1308,15 @@ func TestResponderGetCost(t *testing.T) {
tStart, _ := utils.ParseDate("2013-08-07T17:30:00Z")
tEnd, _ := utils.ParseDate("2013-08-07T17:31:30Z")
cd := engine.CallDescriptor{
Direction: "*out",
TOR: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "+4917621621391",
CallDuration: 90,
TimeStart: tStart,
TimeEnd: tEnd,
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "+4917621621391",
DurationIndex: 90,
TimeStart: tStart,
TimeEnd: tEnd,
}
var cc engine.CallCost
// Simple test that command is executed without errors
@@ -1315,6 +1345,30 @@ func TestGetCallCostLog(t *testing.T) {
}
}
func TestMaxDebitInexistentAcnt(t *testing.T) {
if !*testLocal {
return
}
cc := &engine.CallCost{}
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
Category: "call",
Subject: "INVALID",
Account: "INVALID",
Destination: "1002",
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
}
if err := rater.Call("Responder.MaxDebit", cd, cc); err == nil {
t.Error(err.Error())
}
if err := rater.Call("Responder.Debit", cd, cc); err == nil {
t.Error(err.Error())
}
}
func TestCdrServer(t *testing.T) {
if !*testLocal {
return
@@ -1322,9 +1376,11 @@ func TestCdrServer(t *testing.T) {
httpClient := new(http.Client)
cdrForm1 := url.Values{"accid": []string{"dsafdsaf"}, "cdrhost": []string{"192.168.1.1"}, "reqtype": []string{"rated"}, "direction": []string{"*out"},
"tenant": []string{"cgrates.org"}, "tor": []string{"call"}, "account": []string{"1001"}, "subject": []string{"1001"}, "destination": []string{"1002"},
"setup_time": []string{"2013-11-07T08:42:22Z"},
"answer_time": []string{"2013-11-07T08:42:26Z"}, "duration": []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}}
cdrForm2 := url.Values{"accid": []string{"adsafdsaf"}, "cdrhost": []string{"192.168.1.1"}, "reqtype": []string{"rated"}, "direction": []string{"*out"},
"tenant": []string{"cgrates.org"}, "tor": []string{"call"}, "account": []string{"1001"}, "subject": []string{"1001"}, "destination": []string{"1002"},
"setup_time": []string{"2013-11-07T08:42:23Z"},
"answer_time": []string{"2013-11-07T08:42:26Z"}, "duration": []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}}
for _, cdrForm := range []url.Values{cdrForm1, cdrForm2} {
cdrForm.Set(utils.CDRSOURCE, engine.TEST_SQL)
@@ -1334,23 +1390,28 @@ func TestCdrServer(t *testing.T) {
}
}
/*
func TestExportCdrsToFile(t *testing.T) {
if !*testLocal {
return
}
var reply *utils.ExportedFileCdrs
req := utils.AttrExpFileCdrs{}
if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err == nil || !strings.HasPrefix(err.Error(), utils.ERR_MANDATORY_IE_MISSING) {
t.Error("Failed to detect missing parameter")
}
req.CdrFormat = utils.CDRE_DRYRUN
expectReply := &utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: 2, ExportedCgrIds: []string{utils.FSCgrId("dsafdsaf"), utils.FSCgrId("adsafdsaf")}}
//if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err == nil || !strings.HasPrefix(err.Error(), utils.ERR_MANDATORY_IE_MISSING) {
// t.Error("Failed to detect missing parameter")
//}
dryRun := utils.CDRE_DRYRUN
req.CdrFormat = &dryRun
tm1, _ := utils.ParseTimeDetectLayout("2013-11-07T08:42:22Z")
tm2, _ := utils.ParseTimeDetectLayout("2013-11-07T08:42:23Z")
expectReply := &utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: 2, ExportedCgrIds: []string{utils.Sha1("dsafdsaf", tm1.String()),
utils.Sha1("adsafdsaf", tm2.String())}}
if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil {
t.Error(err.Error())
} else if !reflect.DeepEqual(reply, expectReply) {
t.Errorf("Unexpected reply: %v", reply)
}
/* Need to implement temporary file writing in order to test removal from db, not possible on DRYRUN
Need to implement temporary file writing in order to test removal from db, not possible on DRYRUN
req.RemoveFromDb = true
if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil {
t.Error(err.Error())
@@ -1363,7 +1424,232 @@ func TestExportCdrsToFile(t *testing.T) {
} else if !reflect.DeepEqual(reply, expectReply) {
t.Errorf("Unexpected reply: %v", reply)
}
*/
}
*/
func TestLocalGetCdrs(t *testing.T) {
if !*testLocal {
return
}
var reply []*utils.StoredCdr
req := utils.AttrGetCdrs{}
if err := rater.Call("ApierV1.GetCdrs", req, &reply); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if len(reply) != 2 {
t.Error("Unexpected number of CDRs returned: ", len(reply))
}
}
func TestLocalProcessCdr(t *testing.T) {
if !*testLocal {
return
}
var reply string
cdr := utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf",
CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
}
if err := rater.Call("CDRSV1.ProcessCdr", cdr, &reply); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply received: ", reply)
}
var cdrs []*utils.StoredCdr
req := utils.AttrGetCdrs{}
if err := rater.Call("ApierV1.GetCdrs", req, &cdrs); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if len(cdrs) != 3 {
t.Error("Unexpected number of CDRs returned: ", len(cdrs))
}
}
func TestLocalSetDC(t *testing.T) {
if !*testLocal {
return
}
dcs1 := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
}
attrs := AttrSetDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan", DerivedChargers: dcs1}
var reply string
if err := rater.Call("ApierV1.SetDerivedChargers", attrs, &reply); err != nil {
t.Error("Unexpected error", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply returned", reply)
}
}
func TestLocalGetDC(t *testing.T) {
if !*testLocal {
return
}
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
eDcs := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
}
var dcs utils.DerivedChargers
if err := rater.Call("ApierV1.GetDerivedChargers", attrs, &dcs); err != nil {
t.Error("Unexpected error", err.Error())
} else if !reflect.DeepEqual(dcs, eDcs) {
t.Errorf("Expecting: %v, received: %v", eDcs, dcs)
}
}
func TestLocalRemDC(t *testing.T) {
if !*testLocal {
return
}
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
var reply string
if err := rater.Call("ApierV1.RemDerivedChargers", attrs, &reply); err != nil {
t.Error("Unexpected error", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply returned", reply)
}
}
func TestLocalGetRatingSubjectAliases(t *testing.T) {
if !*testLocal {
return
}
var subjAliases []string
if err := rater.Call("ApierV1.GetRatingSubjectAliases", engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}, &subjAliases); err == nil {
t.Error("Unexpected nil error received")
} else if err.Error() != utils.ERR_NOT_FOUND {
t.Error("Unexpected error", err.Error())
}
}
func TestLocalAddRatingSubjectAliases(t *testing.T) {
if !*testLocal {
return
}
addRtSubjAliases := &AttrAddRatingSubjectAliases{Tenant: "cgrates.org", Subject: "1001", Aliases: []string{"2001", "2002", "2003"}}
var rply string
if err := rater.Call("ApierV1.AddRatingSubjectAliases", addRtSubjAliases, &rply); err != nil {
t.Error("Unexpected error", err.Error())
} else if rply != utils.OK {
t.Error("Unexpected reply: ", rply)
}
var subjAliases []string
expectSubjAliases := sort.StringSlice(addRtSubjAliases.Aliases)
expectSubjAliases.Sort()
if err := rater.Call("ApierV1.GetRatingSubjectAliases", engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}, &subjAliases); err != nil {
t.Error("Unexpected error", err.Error())
} else {
subjAliases := sort.StringSlice(subjAliases)
subjAliases.Sort()
if !reflect.DeepEqual(expectSubjAliases, subjAliases) {
t.Errorf("Expecting: %v, received: %v", expectSubjAliases, subjAliases)
}
}
}
func TestLocalRemRatingSubjectAliases(t *testing.T) {
if !*testLocal {
return
}
tenantRatingSubj := engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}
var rply string
if err := rater.Call("ApierV1.RemRatingSubjectAliases", tenantRatingSubj, &rply); err != nil {
t.Error("Unexpected error", err.Error())
} else if rply != utils.OK {
t.Error("Unexpected reply: ", rply)
}
var subjAliases []string
if err := rater.Call("ApierV1.GetRatingSubjectAliases", engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}, &subjAliases); err == nil {
t.Error("Unexpected nil error received")
} else if err.Error() != utils.ERR_NOT_FOUND {
t.Error("Unexpected error", err.Error())
}
}
func TestLocalGetAccountAliases(t *testing.T) {
if !*testLocal {
return
}
tenantAcnt := engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}
var acntAliases []string
if err := rater.Call("ApierV1.GetAccountAliases", tenantAcnt, &acntAliases); err == nil {
t.Error("Unexpected nil error received")
} else if err.Error() != utils.ERR_NOT_FOUND {
t.Error("Unexpected error", err.Error())
}
}
func TestLocalAddAccountAliases(t *testing.T) {
if !*testLocal {
return
}
addAcntAliases := &AttrAddAccountAliases{Tenant: "cgrates.org", Account: "1001", Aliases: []string{"2001", "2002", "2003"}}
var rply string
if err := rater.Call("ApierV1.AddAccountAliases", addAcntAliases, &rply); err != nil {
t.Error("Unexpected error", err.Error())
} else if rply != utils.OK {
t.Error("Unexpected reply: ", rply)
}
var acntAliases []string
expectAcntAliases := sort.StringSlice(addAcntAliases.Aliases)
expectAcntAliases.Sort()
if err := rater.Call("ApierV1.GetAccountAliases", engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}, &acntAliases); err != nil {
t.Error("Unexpected error", err.Error())
} else {
acntAliases := sort.StringSlice(acntAliases)
acntAliases.Sort()
if !reflect.DeepEqual(expectAcntAliases, acntAliases) {
t.Errorf("Expecting: %v, received: %v", expectAcntAliases, acntAliases)
}
}
}
func TestLocalRemAccountAliases(t *testing.T) {
if !*testLocal {
return
}
tenantAcnt := engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}
var rply string
if err := rater.Call("ApierV1.RemAccountAliases", tenantAcnt, &rply); err != nil {
t.Error("Unexpected error", err.Error())
} else if rply != utils.OK {
t.Error("Unexpected reply: ", rply)
}
var acntAliases []string
if err := rater.Call("ApierV1.GetAccountAliases", engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}, &acntAliases); err == nil {
t.Error("Unexpected nil error received")
} else if err.Error() != utils.ERR_NOT_FOUND {
t.Error("Unexpected error", err.Error())
}
}
func TestLocalGetScheduledActions(t *testing.T) {
if !*testLocal {
return
}
var rply []*ScheduledActions
if err := rater.Call("ApierV1.GetScheduledActions", AttrsGetScheduledActions{}, &rply); err != nil {
t.Error("Unexpected error: ", err.Error)
}
}
func TestLocalGetDataCost(t *testing.T) {
if !*testLocal {
return
}
attrs := AttrGetDataCost{Direction: "*out", Category: "data", Tenant: "cgrates.org", Account: "1001", Subject: "1001", StartTime: time.Now(), Usage: 640113}
var rply *engine.DataCost
if err := rater.Call("ApierV1.GetDataCost", attrs, &rply); err != nil {
t.Error("Unexpected nil error received: ", err.Error())
} else if rply.Cost != 128.0240 {
t.Errorf("Unexpected cost received: %f", rply.Cost)
}
}
// Simply kill the engine after we are done with tests within this file

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,8 +22,8 @@ import (
"fmt"
"github.com/cgrates/cgrates/cdre"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"os"
"path"
"strconv"
"strings"
@@ -34,10 +34,7 @@ import (
func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.ExportedFileCdrs) error {
var tStart, tEnd time.Time
var err error
cdrFormat := strings.ToLower(attr.CdrFormat)
if !utils.IsSliceMember(utils.CdreCdrFormats, cdrFormat) {
return fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "CdrFormat")
}
engine.Logger.Debug(fmt.Sprintf("ExportCdrsToFile: %+v", attr))
if len(attr.TimeStart) != 0 {
if tStart, err = utils.ParseTimeDetectLayout(attr.TimeStart); err != nil {
return err
@@ -48,94 +45,107 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
return err
}
}
fileName := attr.ExportFileName
exportId := attr.ExportId
if len(exportId) == 0 {
exportId = strconv.FormatInt(time.Now().Unix(), 10)
exportTemplate := self.Config.CdreDefaultInstance
if attr.ExportTemplate != nil { // XML Template defined, can be field names or xml reference
if strings.HasPrefix(*attr.ExportTemplate, utils.XML_PROFILE_PREFIX) {
if self.Config.XmlCfgDocument == nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "XmlDocumentNotLoaded")
}
expTplStr := *attr.ExportTemplate
if xmlTemplates := self.Config.XmlCfgDocument.GetCdreCfgs(expTplStr[len(utils.XML_PROFILE_PREFIX):]); xmlTemplates == nil {
return fmt.Errorf("%s:ExportTemplate", utils.ERR_NOT_FOUND)
} else {
exportTemplate = xmlTemplates[expTplStr[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig()
}
} else {
exportTemplate, _ = config.NewDefaultCdreConfig()
if contentFlds, err := config.NewCdreCdrFieldsFromIds(exportTemplate.CdrFormat == utils.CDRE_FIXED_WIDTH,
strings.Split(*attr.ExportTemplate, string(utils.CSV_SEP))...); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
exportTemplate.ContentFields = contentFlds
}
}
}
roundDecimals := attr.RoundingDecimals
if roundDecimals == 0 {
roundDecimals = self.Config.RoundingDecimals
if exportTemplate == nil {
return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING)
}
cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunId, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction,
attr.Tenant, attr.Tor, attr.Account, attr.Subject, attr.DestinationPrefix, tStart, tEnd, attr.SkipErrors, attr.SkipRated)
cdrFormat := exportTemplate.CdrFormat
if attr.CdrFormat != nil {
cdrFormat = strings.ToLower(*attr.CdrFormat)
}
if !utils.IsSliceMember(utils.CdreCdrFormats, cdrFormat) {
return fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "CdrFormat")
}
fieldSep := exportTemplate.FieldSeparator
if attr.FieldSeparator != nil {
fieldSep = *attr.FieldSeparator
}
exportDir := exportTemplate.ExportDir
if attr.ExportDir != nil {
exportDir = *attr.ExportDir
}
exportId := strconv.FormatInt(time.Now().Unix(), 10)
if attr.ExportId != nil {
exportId = *attr.ExportId
}
fileName := fmt.Sprintf("cdre_%s.%s", exportId, cdrFormat)
if attr.ExportFileName != nil {
fileName = *attr.ExportFileName
}
filePath := path.Join(exportDir, fileName)
if cdrFormat == utils.CDRE_DRYRUN {
filePath = utils.CDRE_DRYRUN
}
dataUsageMultiplyFactor := exportTemplate.DataUsageMultiplyFactor
if attr.DataUsageMultiplyFactor != nil {
dataUsageMultiplyFactor = *attr.DataUsageMultiplyFactor
}
costMultiplyFactor := exportTemplate.CostMultiplyFactor
if attr.CostMultiplyFactor != nil {
costMultiplyFactor = *attr.CostMultiplyFactor
}
costShiftDigits := exportTemplate.CostShiftDigits
if attr.CostShiftDigits != nil {
costShiftDigits = *attr.CostShiftDigits
}
roundingDecimals := exportTemplate.CostRoundingDecimals
if attr.RoundDecimals != nil {
roundingDecimals = *attr.RoundDecimals
}
maskDestId := exportTemplate.MaskDestId
if attr.MaskDestinationId != nil {
maskDestId = *attr.MaskDestinationId
}
maskLen := exportTemplate.MaskLength
if attr.MaskLength != nil {
maskLen = *attr.MaskLength
}
cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunIds, attr.TORs, attr.CdrHosts, attr.CdrSources, attr.ReqTypes, attr.Directions,
attr.Tenants, attr.Categories, attr.Accounts, attr.Subjects, attr.DestinationPrefixes, attr.RatedAccounts, attr.RatedSubjects, attr.OrderIdStart, attr.OrderIdEnd,
tStart, tEnd, attr.SkipErrors, attr.SkipRated, false)
if err != nil {
return err
} else if len(cdrs) == 0 {
*reply = utils.ExportedFileCdrs{ExportedFilePath: ""}
return nil
}
switch cdrFormat {
case utils.CDRE_DRYRUN:
exportedIds := make([]string, len(cdrs))
for idxCdr, cdr := range cdrs {
exportedIds[idxCdr] = cdr.CgrId
}
*reply = utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds}
case utils.CDRE_CSV:
if len(fileName) == 0 {
fileName = fmt.Sprintf("cdre_%s.csv", exportId)
}
exportedFields := self.Config.CdreExportedFields
if len(attr.ExportTemplate) != 0 {
if exportedFields, err = config.ParseRSRFields(attr.ExportTemplate); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
}
if len(exportedFields) == 0 {
return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING)
}
filePath := path.Join(self.Config.CdreDir, fileName)
fileOut, err := os.Create(filePath)
if err != nil {
return err
}
defer fileOut.Close()
csvWriter := cdre.NewCsvCdrWriter(fileOut, roundDecimals, exportedFields)
exportedIds := make([]string, 0)
unexportedIds := make(map[string]string)
for _, cdr := range cdrs {
if err := csvWriter.WriteCdr(cdr); err != nil {
unexportedIds[cdr.CgrId] = err.Error()
} else {
exportedIds = append(exportedIds, cdr.CgrId)
}
}
csvWriter.Close()
*reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds, UnexportedCgrIds: unexportedIds}
case utils.CDRE_FIXED_WIDTH:
if len(fileName) == 0 {
fileName = fmt.Sprintf("cdre_%s.fwv", exportId)
}
exportTemplate := self.Config.CdreFWXmlTemplate
if len(attr.ExportTemplate) != 0 && self.Config.XmlCfgDocument != nil {
if xmlTemplate, err := self.Config.XmlCfgDocument.GetCdreFWCfg(attr.ExportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if xmlTemplate != nil {
exportTemplate = xmlTemplate
}
}
if exportTemplate == nil {
return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING)
}
filePath := path.Join(self.Config.CdreDir, fileName)
fileOut, err := os.Create(filePath)
if err != nil {
return err
}
defer fileOut.Close()
fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, roundDecimals)
exportedIds := make([]string, 0)
unexportedIds := make(map[string]string)
for _, cdr := range cdrs {
if err := fww.WriteCdr(cdr); err != nil {
unexportedIds[cdr.CgrId] = err.Error()
} else {
exportedIds = append(exportedIds, cdr.CgrId)
}
}
fww.Close()
*reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds, UnexportedCgrIds: unexportedIds}
cdrexp, err := cdre.NewCdrExporter(cdrs, self.LogDb, exportTemplate, cdrFormat, fieldSep, exportId,
dataUsageMultiplyFactor, costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify)
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if cdrexp.TotalExportedCdrs() == 0 {
*reply = utils.ExportedFileCdrs{ExportedFilePath: ""}
return nil
}
if err := cdrexp.WriteToFile(filePath); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), TotalCost: cdrexp.TotalCost(), FirstOrderId: cdrexp.FirstOrderId(), LastOrderId: cdrexp.LastOrderId()}
if !attr.SuppressCgrIds {
reply.ExportedCgrIds = cdrexp.PositiveExports()
reply.UnexportedCgrIds = cdrexp.NegativeExports()
}
return nil
}

View File

@@ -1,14 +1,14 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
@@ -22,6 +22,7 @@ import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"time"
)
type AttrGetCallCost struct {
@@ -43,3 +44,29 @@ func (apier *ApierV1) GetCallCostLog(attrs AttrGetCallCost, reply *engine.CallCo
}
return nil
}
// Retrieves CDRs based on the filters
func (apier *ApierV1) GetCdrs(attrs utils.AttrGetCdrs, reply *[]*utils.CgrCdrOut) error {
var tStart, tEnd time.Time
var err error
if len(attrs.TimeStart) != 0 {
if tStart, err = utils.ParseTimeDetectLayout(attrs.TimeStart); err != nil {
return err
}
}
if len(attrs.TimeEnd) != 0 {
if tEnd, err = utils.ParseTimeDetectLayout(attrs.TimeEnd); err != nil {
return err
}
}
if cdrs, err := apier.CdrDb.GetStoredCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions,
attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects,
attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.SkipErrors, attrs.SkipRated, false); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
for _, cdr := range cdrs {
*reply = append(*reply, cdr.AsCgrCdrOut())
}
}
return nil
}

62
apier/cdrstatsv1.go Normal file
View File

@@ -0,0 +1,62 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// Interact with Stats server
type CDRStatsV1 struct {
CdrStats *engine.Stats
}
type AttrGetMetrics struct {
StatsQueueId string // Id of the stats instance queried
}
func (sts *CDRStatsV1) GetMetrics(attr AttrGetMetrics, reply *map[string]float64) error {
if len(attr.StatsQueueId) == 0 {
return fmt.Errorf("%s:StatsQueueId", utils.ERR_MANDATORY_IE_MISSING)
}
return sts.CdrStats.GetValues(attr.StatsQueueId, reply)
}
func (sts *CDRStatsV1) GetQueueIds(empty string, reply *[]string) error {
return sts.CdrStats.GetQueueIds(0, reply)
}
func (sts *CDRStatsV1) ReloadQueues(attr utils.AttrCDRStatsReloadQueues, reply *string) error {
if err := sts.CdrStats.ReloadQueues(attr.StatsQueueIds, nil); err != nil {
return err
}
*reply = utils.OK
return nil
}
func (sts *CDRStatsV1) ResetQueues(attr utils.AttrCDRStatsReloadQueues, reply *string) error {
if err := sts.CdrStats.ResetQueues(attr.StatsQueueIds, nil); err != nil {
return err
}
*reply = utils.OK
return nil
}

View File

@@ -0,0 +1,221 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
"os/exec"
"path"
"reflect"
"testing"
"time"
)
var cdrstCfgPath string
var cdrstCfg *config.CGRConfig
var cdrstRpc *rpc.Client
func init() {
cdrstCfgPath = path.Join(*dataDir, "conf", "samples", "cdrstatsv1_local_test.cfg")
cdrstCfg, _ = config.NewCGRConfigFromFile(&cdrstCfgPath)
}
func TestCDRStatsLclInitDataDb(t *testing.T) {
if !*testLocal {
return
}
ratingDb, err := engine.ConfigureRatingStorage(cdrstCfg.RatingDBType, cdrstCfg.RatingDBHost, cdrstCfg.RatingDBPort, cdrstCfg.RatingDBName,
cdrstCfg.RatingDBUser, cdrstCfg.RatingDBPass, cdrstCfg.DBDataEncoding)
if err != nil {
t.Fatal("Cannot connect to dataDb", err)
}
accountDb, err := engine.ConfigureAccountingStorage(cdrstCfg.AccountDBType, cdrstCfg.AccountDBHost, cdrstCfg.AccountDBPort, cdrstCfg.AccountDBName,
cdrstCfg.AccountDBUser, cdrstCfg.AccountDBPass, cdrstCfg.DBDataEncoding)
if err != nil {
t.Fatal("Cannot connect to dataDb", err)
}
for _, db := range []engine.Storage{ratingDb, accountDb} {
if err := db.Flush(); err != nil {
t.Fatal("Cannot reset dataDb", err)
}
}
}
func TestCDRStatsLclStartEngine(t *testing.T) {
if !*testLocal {
return
}
enginePath, err := exec.LookPath("cgr-engine")
if err != nil {
t.Fatal("Cannot find cgr-engine executable")
}
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
engine := exec.Command(enginePath, "-config", cdrstCfgPath)
if err := engine.Start(); err != nil {
t.Fatal("Cannot start cgr-engine: ", err.Error())
}
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time to rater to fire up
}
// Connect rpc client to rater
func TestCDRStatsLclRpcConn(t *testing.T) {
if !*testLocal {
return
}
var err error
cdrstRpc, err = jsonrpc.Dial("tcp", cdrstCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
if err != nil {
t.Fatal("Could not connect to rater: ", err.Error())
}
}
func TestCDRStatsLclGetQueueIds(t *testing.T) {
if !*testLocal {
return
}
var queueIds []string
eQueueIds := []string{"*default"}
if err := cdrstRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
} else if !reflect.DeepEqual(eQueueIds, queueIds) {
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
}
}
func TestCDRStatsLclLoadTariffPlanFromFolder(t *testing.T) {
if !*testLocal {
return
}
reply := ""
// Simple test that command is executed without errors
attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "cdrstats")}
if err := cdrstRpc.Call("ApierV1.LoadTariffPlanFromFolder", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.LoadTariffPlanFromFolder: ", err.Error())
} else if reply != "OK" {
t.Error("Calling ApierV1.LoadTariffPlanFromFolder got reply: ", reply)
}
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups
}
func TestCDRStatsLclGetQueueIds2(t *testing.T) {
if !*testLocal {
return
}
var queueIds []string
eQueueIds := []string{"*default", "CDRST3", "CDRST4"}
if err := cdrstRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
} else if !reflect.DeepEqual(eQueueIds, queueIds) {
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
}
}
func TestCDRStatsLclPostCdrs(t *testing.T) {
if !*testLocal {
return
}
httpClient := new(http.Client)
storedCdrs := []*utils.StoredCdr{
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafa", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(),
AnswerTime: time.Now(), MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafb", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(),
AnswerTime: time.Now(), MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(5) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafc", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(), AnswerTime: time.Now(),
MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(30) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafd", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(), AnswerTime: time.Time{},
MediationRunId: utils.DEFAULT_RUNID,
Usage: time.Duration(0) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
},
}
for _, storedCdr := range storedCdrs {
if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", "127.0.0.1:2080"), storedCdr.AsHttpForm()); err != nil {
t.Error(err.Error())
}
}
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
}
func TestCDRStatsLclGetMetrics1(t *testing.T) {
if !*testLocal {
return
}
var rcvMetrics1 map[string]float64
expectedMetrics1 := map[string]float64{"ASR": 75, "ACD": 15, "ACC": 15}
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "*default"}, &rcvMetrics1); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(expectedMetrics1, rcvMetrics1) {
t.Errorf("Expecting: %v, received: %v", expectedMetrics1, rcvMetrics1)
}
var rcvMetrics2 map[string]float64
expectedMetrics2 := map[string]float64{"ASR": 75, "ACD": 15}
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "CDRST4"}, &rcvMetrics2); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(expectedMetrics2, rcvMetrics2) {
t.Errorf("Expecting: %v, received: %v", expectedMetrics2, rcvMetrics2)
}
}
func TestCDRStatsLclResetMetrics(t *testing.T) {
if !*testLocal {
return
}
var reply string
if err := cdrstRpc.Call("CDRStatsV1.ResetQueues", utils.AttrCDRStatsReloadQueues{StatsQueueIds: []string{"CDRST4"}}, &reply); err != nil {
t.Error("Calling CDRStatsV1.ResetQueues, got error: ", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply received: ", reply)
}
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
var rcvMetrics1 map[string]float64
expectedMetrics1 := map[string]float64{"ASR": 75, "ACD": 15, "ACC": 15}
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "*default"}, &rcvMetrics1); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(expectedMetrics1, rcvMetrics1) {
t.Errorf("Expecting: %v, received: %v", expectedMetrics1, rcvMetrics1)
}
var rcvMetrics2 map[string]float64
expectedMetrics2 := map[string]float64{"ASR": 0, "ACD": 0}
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "CDRST4"}, &rcvMetrics2); err != nil {
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
} else if !reflect.DeepEqual(expectedMetrics2, rcvMetrics2) {
t.Errorf("Expecting: %v, received: %v", expectedMetrics2, rcvMetrics2)
}
}

41
apier/cdrsv1.go Normal file
View File

@@ -0,0 +1,41 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// Receive CDRs via RPC methods
type CDRSV1 struct {
CdrSrv *engine.CDRS
}
func (cdrsrv *CDRSV1) ProcessCdr(cdr *utils.StoredCdr, reply *string) error {
if cdrsrv.CdrSrv == nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "CDRS_NOT_RUNNING")
}
if err := cdrsrv.CdrSrv.ProcessCdr(cdr); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
return nil
}

59
apier/costs.go Normal file
View File

@@ -0,0 +1,59 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"time"
)
type AttrGetDataCost struct {
Direction string
Category string
Tenant, Account, Subject string
StartTime time.Time
Usage int64 // the call duration so far (till TimeEnd)
}
func (apier *ApierV1) GetDataCost(attrs AttrGetDataCost, reply *engine.DataCost) error {
usageAsDuration := time.Duration(attrs.Usage) * time.Second // Convert to seconds to match the loaded rates
cd := engine.CallDescriptor{
Direction: attrs.Direction,
Category: attrs.Category,
Tenant: attrs.Tenant,
Account: attrs.Account,
Subject: attrs.Subject,
TimeStart: attrs.StartTime,
TimeEnd: attrs.StartTime.Add(usageAsDuration),
DurationIndex: usageAsDuration,
TOR: utils.DATA,
}
var cc engine.CallCost
if err := apier.Responder.GetCost(cd, &cc); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if dc, err := cc.ToDataCost(); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if dc != nil {
*reply = *dc
}
return nil
}

82
apier/derivedcharging.go Normal file
View File

@@ -0,0 +1,82 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// Get DerivedChargers applying to our call, appends general configured to account specific ones if that is configured
func (self *ApierV1) GetDerivedChargers(attrs utils.AttrDerivedChargers, reply *utils.DerivedChargers) (err error) {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Direction", "Account", "Subject"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if hDc, err := engine.HandleGetDerivedChargers(self.AccountDb, self.Config, attrs); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if hDc != nil {
*reply = hDc
}
return nil
}
type AttrSetDerivedChargers struct {
Direction, Tenant, Category, Account, Subject string
DerivedChargers utils.DerivedChargers
}
func (self *ApierV1) SetDerivedChargers(attrs AttrSetDerivedChargers, reply *string) (err error) {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Category", "Direction", "Account", "Subject", "DerivedChargers"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
for _, dc := range attrs.DerivedChargers {
if _, err = utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP); err != nil { // Make sure rules are OK before loading in db
return fmt.Errorf("%s:%s", utils.ERR_PARSER_ERROR, err.Error())
}
}
dcKey := utils.DerivedChargersKey(attrs.Direction, attrs.Tenant, attrs.Category, attrs.Account, attrs.Subject)
if err := self.AccountDb.SetDerivedChargers(dcKey, attrs.DerivedChargers); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if err := self.AccountDb.CacheAccounting([]string{}, []string{}, []string{}, []string{engine.DERIVEDCHARGERS_PREFIX + dcKey}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK
return nil
}
type AttrRemDerivedChargers struct {
Direction, Tenant, Category, Account, Subject string
}
func (self *ApierV1) RemDerivedChargers(attrs AttrRemDerivedChargers, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Direction", "Tenant", "Category", "Account", "Subject"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.AccountDb.SetDerivedChargers(utils.DerivedChargersKey(attrs.Direction, attrs.Tenant, attrs.Category, attrs.Account, attrs.Subject), nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
if err := self.AccountDb.CacheAccounting([]string{}, []string{}, []string{}, nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
return nil
}

View File

@@ -0,0 +1,101 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can Storagetribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITH*out ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"reflect"
"testing"
)
var apierDcT *ApierV1
func init() {
dataStorage, _ := engine.NewMapStorage()
cfg, _ := config.NewDefaultCGRConfig()
apierDcT = &ApierV1{AccountDb: engine.AccountingStorage(dataStorage), Config: cfg}
}
func TestGetEmptyDC(t *testing.T) {
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
var dcs utils.DerivedChargers
if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil {
t.Error("Unexpected error", err.Error())
} else if !reflect.DeepEqual(dcs, apierDcT.Config.DerivedChargers) {
t.Error("Returned DerivedChargers not matching the configured ones")
}
}
func TestSetDC(t *testing.T) {
dcs1 := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
}
attrs := AttrSetDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan", DerivedChargers: dcs1}
var reply string
if err := apierDcT.SetDerivedChargers(attrs, &reply); err != nil {
t.Error("Unexpected error", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply returned", reply)
}
}
func TestGetDC(t *testing.T) {
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
eDcs := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
}
var dcs utils.DerivedChargers
if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil {
t.Error("Unexpected error", err.Error())
} else if !reflect.DeepEqual(dcs, eDcs) {
t.Errorf("Expecting: %v, received: %v", eDcs, dcs)
}
}
func TestRemDC(t *testing.T) {
attrs := AttrRemDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan"}
var reply string
if err := apierDcT.RemDerivedChargers(attrs, &reply); err != nil {
t.Error("Unexpected error", err.Error())
} else if reply != utils.OK {
t.Error("Unexpected reply returned", reply)
}
}
func TestGetEmptyDC2(t *testing.T) {
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
var dcs utils.DerivedChargers
if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil {
t.Error("Unexpected error", err.Error())
} else if !reflect.DeepEqual(dcs, apierDcT.Config.DerivedChargers) {
for _, dc := range dcs {
fmt.Printf("Got dc: %v\n", dc)
}
t.Error("Returned DerivedChargers not matching the configured ones")
}
}

View File

@@ -16,16 +16,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package mediator
package apier
import (
"fmt"
"time"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
type MediatorV1 struct {
Medi *Mediator
Medi *engine.Mediator
}
// Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog
@@ -45,7 +47,11 @@ func (self *MediatorV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error
return err
}
}
if err := self.Medi.RateCdrs(tStart, tEnd, attrs.RerateErrors, attrs.RerateRated); err != nil {
//RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string,
//orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool)
if err := self.Medi.RateCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions,
attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects,
attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = utils.OK

151
apier/scheduler.go Normal file
View File

@@ -0,0 +1,151 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package apier
import (
"errors"
"github.com/cgrates/cgrates/utils"
"time"
)
/*
[
{
u'ActionsId': u'BONUS_1',
u'Uuid': u'5b5ba53b40b1d44380cce52379ec5c0d',
u'Weight': 10,
u'Timing': {
u'Timing': {
u'MonthDays': [
],
u'Months': [
],
u'WeekDays': [
],
u'Years': [
2013
],
u'StartTime': u'11: 00: 00',
u'EndTime': u''
},
u'Rating': None,
u'Weight': 0
},
u'AccountIds': [
u'*out: cgrates.org: 1001',
u'*out: cgrates.org: 1002',
u'*out: cgrates.org: 1003',
u'*out: cgrates.org: 1004',
u'*out: cgrates.org: 1005'
],
u'Id': u'PREPAID_10'
},
{
u'ActionsId': u'PREPAID_10',
u'Uuid': u'b16ab12740e2e6c380ff7660e8b55528',
u'Weight': 10,
u'Timing': {
u'Timing': {
u'MonthDays': [
],
u'Months': [
],
u'WeekDays': [
],
u'Years': [
2013
],
u'StartTime': u'11: 00: 00',
u'EndTime': u''
},
u'Rating': None,
u'Weight': 0
},
u'AccountIds': [
u'*out: cgrates.org: 1001',
u'*out: cgrates.org: 1002',
u'*out: cgrates.org: 1003',
u'*out: cgrates.org: 1004',
u'*out: cgrates.org: 1005'
],
u'Id': u'PREPAID_10'
}
]
*/
type AttrsGetScheduledActions struct {
Direction, Tenant, Account string
TimeStart, TimeEnd time.Time // Filter based on next runTime
}
type ScheduledActions struct {
NextRunTime time.Time
Accounts []*utils.DirectionTenantAccount
ActionsId, ActionPlanId, ActionPlanUuid string
}
func (self *ApierV1) GetScheduledActions(attrs AttrsGetScheduledActions, reply *[]*ScheduledActions) error {
schedActions := make([]*ScheduledActions, 0)
if self.Sched == nil {
return errors.New("SCHEDULER_NOT_ENABLED")
}
for _, qActions := range self.Sched.GetQueue() {
sas := &ScheduledActions{ActionsId: qActions.ActionsId, ActionPlanId: qActions.Id, ActionPlanUuid: qActions.Uuid}
sas.NextRunTime = qActions.GetNextStartTime(time.Now())
if !attrs.TimeStart.IsZero() && sas.NextRunTime.Before(attrs.TimeStart) {
continue // Filter here only requests in the filtered interval
}
if !attrs.TimeEnd.IsZero() && (sas.NextRunTime.After(attrs.TimeEnd) || sas.NextRunTime.Equal(attrs.TimeEnd)) {
continue
}
acntFiltersMatch := false
for _, acntKey := range qActions.AccountIds {
directionMatched := len(attrs.Direction) == 0
tenantMatched := len(attrs.Tenant) == 0
accountMatched := len(attrs.Account) == 0
dta, _ := utils.NewDTAFromAccountKey(acntKey)
sas.Accounts = append(sas.Accounts, dta)
// One member matching
if !directionMatched && attrs.Direction == dta.Direction {
directionMatched = true
}
if !tenantMatched && attrs.Tenant == dta.Tenant {
tenantMatched = true
}
if !accountMatched && attrs.Account == dta.Account {
accountMatched = true
}
if directionMatched && tenantMatched && accountMatched {
acntFiltersMatch = true
}
}
if !acntFiltersMatch {
continue
}
schedActions = append(schedActions, sas)
}
*reply = schedActions
return nil
}

View File

@@ -53,7 +53,7 @@ func (self *ApierV1) GetTPDestination(attrs AttrGetTPDestination, reply *utils.T
} else if len(dsts) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = utils.TPDestination{attrs.TPid, dsts[0].Id, dsts[0].Prefixes}
*reply = utils.TPDestination{attrs.TPid, dsts[attrs.DestinationId].Id, dsts[attrs.DestinationId].Prefixes}
}
return nil
}

View File

@@ -29,7 +29,7 @@ import (
// Creates a new RatingProfile within a tariff plan
func (self *ApierV1) SetTPRatingProfile(attrs utils.TPRatingProfile, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "TOR", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Category", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.SetTPRatingProfiles(attrs.TPid, map[string]*utils.TPRatingProfile{attrs.KeyId(): &attrs}); err != nil {
@@ -78,7 +78,7 @@ func (self *ApierV1) GetTPRatingProfileLoadIds(attrs utils.AttrTPRatingProfileId
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_RATE_PROFILES, "loadid", map[string]string{
"tenant": attrs.Tenant,
"tor": attrs.TOR,
"tor": attrs.Category,
"direction": attrs.Direction,
"subject": attrs.Subject,
}); err != nil {
@@ -96,7 +96,7 @@ func (self *ApierV1) RemTPRatingProfile(attrs utils.TPRatingProfile, reply *stri
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "TOR", "Direction", "Subject"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_RATE_PROFILES, attrs.TPid, attrs.LoadId, attrs.Tenant, attrs.TOR, attrs.Direction, attrs.Subject); err != nil {
if err := self.StorDb.RemTPData(utils.TBL_TP_RATE_PROFILES, attrs.TPid, attrs.LoadId, attrs.Tenant, attrs.Category, attrs.Direction, attrs.Subject); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"

View File

@@ -38,7 +38,7 @@ var fscsvCfg *config.CGRConfig
func init() {
fscsvCfgPath = path.Join(*dataDir, "tutorials", "fs_csv", "cgrates", "etc", "cgrates", "cgrates.cfg")
fscsvCfg, _ = config.NewCGRConfig(&fscsvCfgPath)
fscsvCfg, _ = config.NewCGRConfigFromFile(&fscsvCfgPath)
}
// Remove here so they can be properly created by init script
@@ -46,7 +46,7 @@ func TestFsCsvRemoveDirs(t *testing.T) {
if !*testLocal {
return
}
for _, pathDir := range []string{cfg.CdreDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
if err := os.RemoveAll(pathDir); err != nil {
t.Fatal("Error removing folder: ", pathDir, err)
}
@@ -67,7 +67,7 @@ func TestFsCsvCreateTables(t *testing.T) {
} else {
mysql = d.(*engine.MySQLStorage)
}
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
t.Fatal("Error on mysql creation: ", err.Error())
return // No point in going further
@@ -182,11 +182,11 @@ func TestFsCsvGetAccount(t *testing.T) {
return
}
var reply *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 { // We expect 11.5 since we have added in the previous test 1.5
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 { // We expect 11.5 since we have added in the previous test 1.5
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
}
@@ -197,15 +197,15 @@ func TestFsCsvCall1(t *testing.T) {
tStart := time.Date(2014, 01, 15, 6, 0, 0, 0, time.UTC)
tEnd := time.Date(2014, 01, 15, 6, 0, 35, 0, time.UTC)
cd := engine.CallDescriptor{
Direction: "*out",
TOR: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
CallDuration: 35,
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
DurationIndex: 35,
}
var cc engine.CallCost
// Make sure the cost is what we expect it is
@@ -222,26 +222,26 @@ func TestFsCsvCall1(t *testing.T) {
}
// Make sure the account was debited correctly for the first loop index (ConnectFee included)
var reply *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 9.4 { // We expect 11.5 since we have added in the previous test 1.5
t.Errorf("Calling ApierV1.GetAccount expected: 9.4, received: %f", reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 9.4 { // We expect 11.5 since we have added in the previous test 1.5
t.Errorf("Calling ApierV1.GetAccount expected: 9.4, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
} else if len(reply.UnitCounters) != 1 ||
utils.Round(reply.UnitCounters[0].Balances[0].Value, 2, utils.ROUNDING_MIDDLE) != 0.6 { // Make sure we correctly count usage
t.Errorf("Received unexpected UnitCounters: %v", reply.UnitCounters)
}
cd = engine.CallDescriptor{
Direction: "*out",
TOR: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
CallDuration: 35,
LoopIndex: 1, // Should not charge ConnectFee
Direction: "*out",
Category: "call",
Tenant: "cgrates.org",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: tStart,
TimeEnd: tEnd,
DurationIndex: 35,
LoopIndex: 1, // Should not charge ConnectFee
}
// Make sure debit charges what cost returned
if err := rater.Call("Responder.MaxDebit", cd, &cc); err != nil {
@@ -253,8 +253,8 @@ func TestFsCsvCall1(t *testing.T) {
var reply2 *engine.Account
if err := rater.Call("ApierV1.GetAccount", attrs, &reply2); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else if utils.Round(reply2.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue(), 2, utils.ROUNDING_MIDDLE) != 9.20 {
t.Errorf("Calling ApierV1.GetAccount expected: 9.2, received: %f", reply2.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
} else if utils.Round(reply2.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue(), 2, utils.ROUNDING_MIDDLE) != 9.20 {
t.Errorf("Calling ApierV1.GetAccount expected: 9.2, received: %f", reply2.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
} else if len(reply2.UnitCounters) != 1 ||
utils.Round(reply2.UnitCounters[0].Balances[0].Value, 2, utils.ROUNDING_MIDDLE) != 0.8 { // Make sure we correctly count usage
t.Errorf("Received unexpected UnitCounters: %v", reply2.UnitCounters)

View File

@@ -41,7 +41,7 @@ var waitFs = flag.Int("wait_fs", 500, "Number of miliseconds to wait for FreeSWI
func init() {
fsjsonCfgPath = path.Join(*dataDir, "tutorials", "fs_json", "cgrates", "etc", "cgrates", "cgrates.cfg")
fsjsonCfg, _ = config.NewCGRConfig(&fsjsonCfgPath)
fsjsonCfg, _ = config.NewCGRConfigFromFile(&fsjsonCfgPath)
}
// Remove here so they can be properly created by init script
@@ -49,7 +49,7 @@ func TestFsJsonRemoveDirs(t *testing.T) {
if !*testLocal {
return
}
for _, pathDir := range []string{fsjsonCfg.CdreDir, fsjsonCfg.HistoryDir} {
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, fsjsonCfg.HistoryDir} {
if err := os.RemoveAll(pathDir); err != nil {
t.Fatal("Error removing folder: ", pathDir, err)
}
@@ -70,11 +70,9 @@ func TestFsJsonCreateTables(t *testing.T) {
} else {
mysql = d.(*engine.MySQLStorage)
}
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL} {
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
t.Fatal("Error on mysql creation: ", err.Error())
return // No point in going further
}
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, engine.CREATE_CDRS_TABLES_SQL)); err != nil {
t.Fatal("Error on mysql creation: ", err.Error())
return // No point in going further
}
for _, tbl := range []string{utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA} {
if _, err := mysql.Db.Query(fmt.Sprintf("SELECT 1 from %s", tbl)); err != nil {
@@ -171,7 +169,7 @@ func TestFsJsonLoadTariffPlans(t *testing.T) {
}
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 2, RatingProfiles: 2, Actions: 5, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1}
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 3, Actions: 5, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1}
var args utils.AttrCacheStats
if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
@@ -185,17 +183,17 @@ func TestFsJsonGetAccount1001(t *testing.T) {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
}
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 2 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 2 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
}
blncLst := acnt.BalanceMap[attrs.BalanceType+attrs.Direction]
blncLst := acnt.BalanceMap[engine.CREDIT+attrs.Direction]
for _, blnc := range blncLst {
if len(blnc.SharedGroup) == 0 && blnc.Value != 5 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
@@ -210,17 +208,17 @@ func TestFsJsonGetAccount1002(t *testing.T) {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1002", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1002", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
}
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
if blnc.Value != 10 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
@@ -231,17 +229,17 @@ func TestFsJsonGetAccount1003(t *testing.T) {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1003", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1003", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
}
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
if blnc.Value != 10 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
@@ -252,17 +250,17 @@ func TestFsJsonGetAccount1004(t *testing.T) {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1004", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1004", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
}
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
if blnc.Value != 10 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
@@ -273,7 +271,7 @@ func TestFsJsonGetAccount1006(t *testing.T) {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1006", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1006", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err == nil {
t.Error("Got no error when querying unexisting balance")
}
@@ -284,17 +282,17 @@ func TestFsJsonGetAccount1007(t *testing.T) {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1007", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1007", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
}
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 0 {
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 0 {
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
}
blncLst := acnt.BalanceMap[attrs.BalanceType+attrs.Direction]
blncLst := acnt.BalanceMap[engine.CREDIT+attrs.Direction]
for _, blnc := range blncLst {
if len(blnc.SharedGroup) == 0 && blnc.Value != 0 { // General balance
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
@@ -311,12 +309,12 @@ func TestMaxCallDuration(t *testing.T) {
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Category: "call",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
}
var remainingDurationFloat float64
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
@@ -330,12 +328,12 @@ func TestMaxCallDuration(t *testing.T) {
cd = engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Category: "call",
Subject: "1002",
Account: "1002",
Destination: "1001",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
}
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
@@ -348,12 +346,12 @@ func TestMaxCallDuration(t *testing.T) {
cd = engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Category: "call",
Subject: "1006",
Account: "1006",
Destination: "1001",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
}
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
@@ -367,12 +365,12 @@ func TestMaxCallDuration(t *testing.T) {
cd = engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Category: "call",
Subject: "1007",
Account: "1007",
Destination: "1001",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
}
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
@@ -393,19 +391,19 @@ func TestMaxDebit1001(t *testing.T) {
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Category: "call",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(time.Duration(10) * time.Second),
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
}
if err := rater.Call("Responder.MaxDebit", cd, cc); err != nil {
t.Error(err.Error())
} else if cc.GetDuration() > time.Duration(1)*time.Minute {
t.Errorf("Unexpected call duration received: %v", cc.GetDuration())
}
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else {
@@ -432,12 +430,12 @@ func TestMaxDebit1007(t *testing.T) {
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Category: "call",
Subject: "1007",
Account: "1007",
Destination: "1002",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(time.Duration(10) * time.Second),
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
}
if err := rater.Call("Responder.MaxDebit", cd, cc); err != nil {
t.Error(err.Error())
@@ -445,7 +443,7 @@ func TestMaxDebit1007(t *testing.T) {
t.Errorf("Unexpected call duration received: %v", cc.GetDuration())
}
// Debit out of shared balance should reflect in the 1001 instead of 1007
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else {
@@ -462,17 +460,17 @@ func TestMaxDebit1007(t *testing.T) {
}
}
// Make sure 1007 remains the same
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "1007", BalanceType: "*monetary", Direction: "*out"}
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1007", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
}
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 0 {
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 0 {
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
}
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
if len(blnc.SharedGroup) == 0 { // General balance
t.Errorf("Unexpected general balance: %f", blnc.Value)
} else if blnc.SharedGroup == "SHARED_A" && blnc.Value != 0 {
@@ -480,6 +478,30 @@ func TestMaxDebit1007(t *testing.T) {
}
}
func TestDerivedChargers1001(t *testing.T) {
if !*testLocal {
return
}
attrs := &utils.AttrDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001"}
expectedDCs := utils.DerivedChargers([]*utils.DerivedCharger{
&utils.DerivedCharger{RunId: "fs_json_run", ReqTypeField: "^rated", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
AccountField: "*default", SubjectField: "^1002", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
})
var rcvRspDCs utils.DerivedChargers
if err := rater.Call("Responder.GetDerivedChargers", attrs, &rcvRspDCs); err != nil {
t.Error(err.Error())
} else if !reflect.DeepEqual(expectedDCs, rcvRspDCs) {
t.Errorf("Expected: %v: received: %v", expectedDCs, rcvRspDCs)
}
// Make sure that over Apier we get the same result as over Responder
var rcvApierDCs utils.DerivedChargers
if err := rater.Call("ApierV1.GetDerivedChargers", attrs, &rcvApierDCs); err != nil {
t.Error(err.Error())
} else if !reflect.DeepEqual(rcvRspDCs, rcvApierDCs) {
t.Errorf("Expected: %v: received: %v", rcvRspDCs, rcvApierDCs)
}
}
// Simply kill the engine after we are done with tests within this file
func TestFsJsonStopEngine(t *testing.T) {
if !*testLocal {

View File

@@ -31,11 +31,17 @@ type timestampedValue struct {
value interface{}
}
const (
PREFIX_LEN = 4
)
var (
xcache = make(map[string]expiringCacheEntry)
xMux sync.RWMutex
cache = make(map[string]timestampedValue)
mux sync.RWMutex
xcache = make(map[string]expiringCacheEntry)
xMux sync.RWMutex
cache = make(map[string]timestampedValue)
mux sync.RWMutex
cMux sync.Mutex
counters = make(map[string]int64)
)
// The main function to cache with expiration
@@ -45,6 +51,10 @@ func (xe *XEntry) XCache(key string, expire time.Duration, value expiringCacheEn
xe.expireDuration = expire
xe.timestamp = time.Now()
xMux.Lock()
if _, ok := xcache[key]; !ok {
// only count if the key is not already there
count(key)
}
xcache[key] = value
xMux.Unlock()
go xe.expire()
@@ -60,7 +70,10 @@ func (xe *XEntry) expire() {
<-xe.t.C
if !xe.keepAlive {
xMux.Lock()
delete(xcache, xe.key)
if _, ok := xcache[xe.key]; ok {
delete(xcache, xe.key)
descount(xe.key)
}
xMux.Unlock()
}
}
@@ -98,6 +111,10 @@ func GetXCached(key string) (ece expiringCacheEntry, err error) {
func Cache(key string, value interface{}) {
mux.Lock()
defer mux.Unlock()
if _, ok := cache[key]; !ok {
// only count if the key is not already there
count(key)
}
cache[key] = timestampedValue{time.Now(), value}
}
@@ -117,10 +134,6 @@ func GetKeyAge(key string) (time.Duration, error) {
if r, ok := cache[key]; ok {
return time.Since(r.timestamp), nil
}
return 0, errors.New("not found")
}
func GetXKeyAge(key string) (time.Duration, error) {
xMux.RLock()
defer xMux.RUnlock()
if r, ok := xcache[key]; ok {
@@ -131,33 +144,34 @@ func GetXKeyAge(key string) (time.Duration, error) {
func RemKey(key string) {
mux.Lock()
defer mux.Unlock()
delete(cache, key)
}
func RemPrefixKey(prefix string) {
mux.Lock()
defer mux.Unlock()
for key, _ := range cache {
if strings.HasPrefix(key, prefix) {
delete(cache, key)
}
if _, ok := cache[key]; ok {
delete(cache, key)
descount(key)
}
}
func XRemKey(key string) {
mux.Unlock()
xMux.Lock()
defer xMux.Unlock()
if r, ok := xcache[key]; ok {
if r.timer() != nil {
r.timer().Stop()
}
}
delete(xcache, key)
if _, ok := xcache[key]; ok {
delete(xcache, key)
descount(key)
}
xMux.Unlock()
}
func XRemPrefixKey(prefix string) {
func RemPrefixKey(prefix string) {
mux.Lock()
for key, _ := range cache {
if strings.HasPrefix(key, prefix) {
delete(cache, key)
descount(key)
}
}
mux.Unlock()
xMux.Lock()
defer xMux.Unlock()
for key, _ := range xcache {
if strings.HasPrefix(key, prefix) {
if r, ok := xcache[key]; ok {
@@ -166,42 +180,102 @@ func XRemPrefixKey(prefix string) {
}
}
delete(xcache, key)
descount(key)
}
}
xMux.Unlock()
}
// Delete all keys from expiraton cache
func XFlush() {
func GetAllEntries(prefix string) map[string]interface{} {
mux.Lock()
result := make(map[string]interface{})
for key, timestampedValue := range cache {
if strings.HasPrefix(key, prefix) {
result[key] = timestampedValue.value
}
}
mux.Unlock()
xMux.Lock()
for key, value := range xcache {
if strings.HasPrefix(key, prefix) {
result[key] = value
}
}
xMux.Unlock()
return result
}
// Delete all keys from cache
func Flush() {
mux.Lock()
cache = make(map[string]timestampedValue)
mux.Unlock()
xMux.Lock()
defer xMux.Unlock()
for _, v := range xcache {
if v.timer() != nil {
v.timer().Stop()
}
}
xcache = make(map[string]expiringCacheEntry)
xMux.Unlock()
cMux.Lock()
counters = make(map[string]int64)
cMux.Unlock()
}
// Delete all keys from cache
func Flush() {
mux.Lock()
defer mux.Unlock()
cache = make(map[string]timestampedValue)
func CountEntries(prefix string) (result int64) {
if _, ok := counters[prefix]; ok {
return counters[prefix]
}
return 0
}
func CountEntries(prefix string) (result int) {
// increments the counter for the specified key prefix
func count(key string) {
if len(key) < PREFIX_LEN {
return
}
cMux.Lock()
defer cMux.Unlock()
prefix := key[:PREFIX_LEN]
if _, ok := counters[prefix]; ok {
// increase the value
counters[prefix] += 1
} else {
counters[prefix] = 1
}
}
// decrements the counter for the specified key prefix
func descount(key string) {
if len(key) < PREFIX_LEN {
return
}
cMux.Lock()
defer cMux.Unlock()
prefix := key[:PREFIX_LEN]
if value, ok := counters[prefix]; ok && value > 0 {
counters[prefix] -= 1
}
}
func GetEntriesKeys(prefix string) (keys []string) {
mux.RLock()
defer mux.RUnlock()
for key, _ := range cache {
if strings.HasPrefix(key, prefix) {
result++
keys = append(keys, key)
}
}
return
}
func XCountEntries(prefix string) (result int) {
func XGetEntriesKeys(prefix string) (keys []string) {
xMux.RLock()
defer xMux.RUnlock()
for key, _ := range xcache {
if strings.HasPrefix(key, prefix) {
result++
keys = append(keys, key)
}
}
return

View File

@@ -57,7 +57,7 @@ func TestFlush(t *testing.T) {
a := &myStruct{data: "mama are mere"}
a.XCache("mama", 10*time.Second, a)
time.Sleep(1000 * time.Millisecond)
XFlush()
Flush()
b, err := GetXCached("mama")
if err == nil || b != nil {
t.Error("Error expiring data")
@@ -67,7 +67,7 @@ func TestFlush(t *testing.T) {
func TestFlushNoTimout(t *testing.T) {
a := &myStruct{data: "mama are mere"}
a.XCache("mama", 10*time.Second, a)
XFlush()
Flush()
b, err := GetXCached("mama")
if err == nil || b != nil {
t.Error("Error expiring data")
@@ -75,12 +75,12 @@ func TestFlushNoTimout(t *testing.T) {
}
func TestRemKey(t *testing.T) {
Cache("t1", "test")
if t1, err := GetCached("t1"); err != nil || t1 != "test" {
Cache("t11_mm", "test")
if t1, err := GetCached("t11_mm"); err != nil || t1 != "test" {
t.Error("Error setting cache")
}
RemKey("t1")
if t1, err := GetCached("t1"); err == nil || t1 == "test" {
RemKey("t11_mm")
if t1, err := GetCached("t11_mm"); err == nil || t1 == "test" {
t.Error("Error removing cached key")
}
}
@@ -91,7 +91,7 @@ func TestXRemKey(t *testing.T) {
if t1, err := GetXCached("mama"); err != nil || t1 != a {
t.Error("Error setting xcache")
}
XRemKey("mama")
RemKey("mama")
if t1, err := GetXCached("mama"); err == nil || t1 == a {
t.Error("Error removing xcached key: ", err, t1)
}
@@ -134,10 +134,21 @@ func TestXRemPrefixKey(t *testing.T) {
a.XCache("x_t1", 10*time.Second, a)
a.XCache("y_t1", 10*time.Second, a)
XRemPrefixKey("x_")
RemPrefixKey("x_")
_, errX := GetXCached("x_t1")
_, errY := GetXCached("y_t1")
if errX == nil || errY != nil {
t.Error("Error removing prefix: ", errX, errY)
}
}
func TestCount(t *testing.T) {
Cache("dst_A1", "1")
Cache("dst_A2", "2")
Cache("rpf_A3", "3")
Cache("dst_A4", "4")
Cache("dst_A5", "5")
if CountEntries("dst_") != 4 {
t.Error("Error countiong entries: ", CountEntries("dst_"))
}
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,7 +21,6 @@ package cdrc
import (
"bufio"
"encoding/csv"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -31,9 +30,8 @@ import (
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/howeyc/fsnotify"
@@ -44,145 +42,120 @@ const (
FS_CSV = "freeswitch_csv"
)
func NewCdrc(config *config.CGRConfig, cdrServer *cdrs.CDRS) (*Cdrc, error) {
cdrc := &Cdrc{cgrCfg: config, cdrServer: cdrServer}
// Before processing, make sure in and out folders exist
for _, dir := range []string{cdrc.cgrCfg.CdrcCdrInDir, cdrc.cgrCfg.CdrcCdrOutDir} {
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
return nil, fmt.Errorf("Folder %s does not exist", dir)
}
func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField, cdrServer *engine.CDRS) (*Cdrc, error) {
if len(csvSep) != 1 {
return nil, fmt.Errorf("Unsupported csv separator: %s", csvSep)
}
if err := cdrc.parseFieldsConfig(); err != nil {
return nil, err
csvSepRune, _ := utf8.DecodeRune([]byte(csvSep))
cdrc := &Cdrc{cdrsAddress: cdrsAddress, cdrType: cdrType, cdrInDir: cdrInDir, cdrOutDir: cdrOutDir,
cdrSourceId: cdrSourceId, runDelay: runDelay, csvSep: csvSepRune, cdrFields: cdrFields, cdrServer: cdrServer}
// Before processing, make sure in and out folders exist
for _, dir := range []string{cdrc.cdrInDir, cdrc.cdrOutDir} {
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
return nil, fmt.Errorf("Nonexistent folder: %s", dir)
}
}
cdrc.httpClient = new(http.Client)
return cdrc, nil
}
type Cdrc struct {
cgrCfg *config.CGRConfig
cdrServer *cdrs.CDRS
cfgCdrFields map[string]string // Key is the name of the field
httpClient *http.Client
cdrsAddress,
cdrType,
cdrInDir,
cdrOutDir,
cdrSourceId string
runDelay time.Duration
csvSep rune
cdrFields map[string][]*utils.RSRField
cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case
httpClient *http.Client
}
// When called fires up folder monitoring, either automated via inotify or manual by sleeping between processing
func (self *Cdrc) Run() error {
if self.cgrCfg.CdrcRunDelay == time.Duration(0) { // Automated via inotify
if self.runDelay == time.Duration(0) { // Automated via inotify
return self.trackCDRFiles()
}
// No automated, process and sleep approach
for {
self.processCdrDir()
time.Sleep(self.cgrCfg.CdrcRunDelay)
time.Sleep(self.runDelay)
}
}
// Loads all fields (primary and extra) into cfgCdrFields, do some pre-checks (eg: in case of csv make sure that values are integers)
func (self *Cdrc) parseFieldsConfig() error {
var err error
self.cfgCdrFields = map[string]string{
utils.ACCID: self.cgrCfg.CdrcAccIdField,
utils.REQTYPE: self.cgrCfg.CdrcReqTypeField,
utils.DIRECTION: self.cgrCfg.CdrcDirectionField,
utils.TENANT: self.cgrCfg.CdrcTenantField,
utils.TOR: self.cgrCfg.CdrcTorField,
utils.ACCOUNT: self.cgrCfg.CdrcAccountField,
utils.SUBJECT: self.cgrCfg.CdrcSubjectField,
utils.DESTINATION: self.cgrCfg.CdrcDestinationField,
utils.SETUP_TIME: self.cgrCfg.CdrcSetupTimeField,
utils.ANSWER_TIME: self.cgrCfg.CdrcAnswerTimeField,
utils.DURATION: self.cgrCfg.CdrcDurationField,
}
// Add extra fields here, config extra fields in the form of []string{"fieldName1:indxInCsv1","fieldName2: indexInCsv2"}
for _, fieldWithIdx := range self.cgrCfg.CdrcExtraFields {
splt := strings.Split(fieldWithIdx, ":")
if len(splt) != 2 {
return errors.New("Cannot parse cdrc.extra_fields")
}
if utils.IsSliceMember(utils.PrimaryCdrFields, splt[0]) {
return errors.New("Extra cdrc.extra_fields overwriting primary fields")
}
self.cfgCdrFields[splt[0]] = splt[1]
}
// Fields populated, do some sanity checks here
for cdrField, cfgVal := range self.cfgCdrFields {
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) && !strings.HasPrefix(cfgVal, utils.STATIC_VALUE_PREFIX) {
if _, err = strconv.Atoi(cfgVal); err != nil {
return fmt.Errorf("Cannot parse configuration field %s into integer", cdrField)
}
}
}
return nil
}
// Takes the record out of csv and turns it into http form which can be posted
func (self *Cdrc) recordAsStoredCdr(record []string) (*utils.StoredCdr, error) {
ratedCdr := &utils.StoredCdr{CdrSource: self.cgrCfg.CdrcSourceId, ExtraFields: map[string]string{}, Cost: -1}
func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
storedCdr := &utils.StoredCdr{CdrHost: "0.0.0.0", CdrSource: self.cdrSourceId, ExtraFields: make(map[string]string), Cost: -1}
var err error
for cfgFieldName, cfgFieldVal := range self.cfgCdrFields {
for cfgFieldName, cfgFieldRSRs := range self.cdrFields {
var fieldVal string
if strings.HasPrefix(cfgFieldVal, utils.STATIC_VALUE_PREFIX) {
fieldVal = cfgFieldVal[1:]
} else if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) {
if cfgFieldIdx, err := strconv.Atoi(cfgFieldVal); err != nil { // Should in theory never happen since we have already parsed config
return nil, err
} else if len(record) <= cfgFieldIdx {
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
} else {
fieldVal = record[cfgFieldIdx]
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) {
for _, cfgFieldRSR := range cfgFieldRSRs {
if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) {
fieldVal += cfgFieldRSR.ParseValue("PLACEHOLDER")
} else { // Dynamic value extracted using index
if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
} else {
fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx])
}
}
}
} else { // Modify here when we add more supported cdr formats
fieldVal = "UNKNOWN"
}
switch cfgFieldName {
case utils.ACCID:
ratedCdr.CgrId = utils.FSCgrId(fieldVal)
ratedCdr.AccId = fieldVal
case utils.REQTYPE:
ratedCdr.ReqType = fieldVal
case utils.DIRECTION:
ratedCdr.Direction = fieldVal
case utils.TENANT:
ratedCdr.Tenant = fieldVal
case utils.TOR:
ratedCdr.TOR = fieldVal
storedCdr.TOR = fieldVal
case utils.ACCID:
storedCdr.AccId = fieldVal
case utils.REQTYPE:
storedCdr.ReqType = fieldVal
case utils.DIRECTION:
storedCdr.Direction = fieldVal
case utils.TENANT:
storedCdr.Tenant = fieldVal
case utils.CATEGORY:
storedCdr.Category = fieldVal
case utils.ACCOUNT:
ratedCdr.Account = fieldVal
storedCdr.Account = fieldVal
case utils.SUBJECT:
ratedCdr.Subject = fieldVal
storedCdr.Subject = fieldVal
case utils.DESTINATION:
ratedCdr.Destination = fieldVal
storedCdr.Destination = fieldVal
case utils.SETUP_TIME:
if ratedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error())
if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error())
}
case utils.ANSWER_TIME:
if ratedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error())
if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error())
}
case utils.DURATION:
if ratedCdr.Duration, err = utils.ParseDurationWithSecs(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse duration field, err: %s", err.Error())
case utils.USAGE:
if storedCdr.Usage, err = utils.ParseDurationWithNanosecs(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse duration field with value: %s, err: %s", fieldVal, err.Error())
}
default: // Extra fields will not match predefined so they all show up here
ratedCdr.ExtraFields[cfgFieldName] = fieldVal
storedCdr.ExtraFields[cfgFieldName] = fieldVal
}
}
return ratedCdr, nil
storedCdr.CgrId = utils.Sha1(storedCdr.AccId, storedCdr.SetupTime.String())
return storedCdr, nil
}
// One run over the CDR folder
func (self *Cdrc) processCdrDir() error {
engine.Logger.Info(fmt.Sprintf("<Cdrc> Parsing folder %s for CDR files.", self.cgrCfg.CdrcCdrInDir))
filesInDir, _ := ioutil.ReadDir(self.cgrCfg.CdrcCdrInDir)
engine.Logger.Info(fmt.Sprintf("<Cdrc> Parsing folder %s for CDR files.", self.cdrInDir))
filesInDir, _ := ioutil.ReadDir(self.cdrInDir)
for _, file := range filesInDir {
if self.cgrCfg.CdrcCdrType != FS_CSV || path.Ext(file.Name()) != ".csv" {
if err := self.processFile(path.Join(self.cgrCfg.CdrcCdrInDir, file.Name())); err != nil {
return err
}
if self.cdrType != FS_CSV || path.Ext(file.Name()) != ".csv" {
go func() { //Enable async processing here
if err := self.processFile(path.Join(self.cdrInDir, file.Name())); err != nil {
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", file, err.Error()))
}
}()
}
}
return nil
@@ -195,18 +168,20 @@ func (self *Cdrc) trackCDRFiles() (err error) {
return
}
defer watcher.Close()
err = watcher.Watch(self.cgrCfg.CdrcCdrInDir)
err = watcher.Watch(self.cdrInDir)
if err != nil {
return
}
engine.Logger.Info(fmt.Sprintf("<Cdrc> Monitoring %s for file moves.", self.cgrCfg.CdrcCdrInDir))
engine.Logger.Info(fmt.Sprintf("<Cdrc> Monitoring %s for file moves.", self.cdrInDir))
for {
select {
case ev := <-watcher.Event:
if ev.IsCreate() && (self.cgrCfg.CdrcCdrType != FS_CSV || path.Ext(ev.Name) != ".csv") {
if err = self.processFile(ev.Name); err != nil {
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", ev.Name, err.Error()))
}
if ev.IsCreate() && (self.cdrType != FS_CSV || path.Ext(ev.Name) != ".csv") {
go func() { //Enable async processing here
if err = self.processFile(ev.Name); err != nil {
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", ev.Name, err.Error()))
}
}()
}
case err := <-watcher.Error:
engine.Logger.Err(fmt.Sprintf("Inotify error: %s", err.Error()))
@@ -225,37 +200,43 @@ func (self *Cdrc) processFile(filePath string) error {
return err
}
csvReader := csv.NewReader(bufio.NewReader(file))
csvReader.Comma = self.csvSep
procRowNr := 0
timeStart := time.Now()
for {
record, err := csvReader.Read()
if err != nil && err == io.EOF {
break // End of file
} else if err != nil {
}
procRowNr += 1 // Only increase if not end of file
if err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
continue // Other csv related errors, ignore
}
rawCdr, err := self.recordAsStoredCdr(record)
storedCdr, err := self.recordToStoredCdr(record)
if err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
continue
}
if self.cgrCfg.CdrcCdrs == utils.INTERNAL {
if err := self.cdrServer.ProcessRawCdr(rawCdr); err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, error: %s", err.Error()))
if self.cdrsAddress == utils.INTERNAL {
if err := self.cdrServer.ProcessCdr(storedCdr); err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, row: %d, error: %s", procRowNr, err.Error()))
continue
}
} else { // CDRs listening on IP
if _, err := self.httpClient.PostForm(fmt.Sprintf("http://%s/cgr", self.cgrCfg.HTTPListen), rawCdr.AsRawCdrHttpForm()); err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, error: %s", err.Error()))
if _, err := self.httpClient.PostForm(fmt.Sprintf("http://%s/cgr", self.cdrsAddress), storedCdr.AsHttpForm()); err != nil {
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, row: %d, error: %s", procRowNr, err.Error()))
continue
}
}
}
// Finished with file, move it to processed folder
newPath := path.Join(self.cgrCfg.CdrcCdrOutDir, fn)
newPath := path.Join(self.cdrOutDir, fn)
if err := os.Rename(filePath, newPath); err != nil {
engine.Logger.Err(err.Error())
return err
}
engine.Logger.Info(fmt.Sprintf("Finished processing %s, moved to %s", fn, newPath))
engine.Logger.Info(fmt.Sprintf("Finished processing %s, moved to %s. Total records processed: %d, run duration: %s",
fn, newPath, procRowNr, time.Now().Sub(timeStart)))
return nil
}

View File

@@ -26,8 +26,8 @@ import (
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"io/ioutil"
"os/exec"
"os"
"os/exec"
"path"
"testing"
"time"
@@ -56,7 +56,7 @@ var waitRater = flag.Int("wait_rater", 300, "Number of miliseconds to wait for r
func init() {
cfgPath = path.Join(*dataDir, "conf", "samples", "apier_local_test.cfg")
cfg, _ = config.NewCGRConfig(&cfgPath)
cfg, _ = config.NewCGRConfigFromFile(&cfgPath)
}
var fileContent1 = `accid11,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
@@ -70,13 +70,18 @@ accid22,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:0
#accid1,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
accid23,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1`
var fileContent3 = `accid31;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1
accid32;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1
#accid1;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1
accid33;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1`
func startEngine() error {
enginePath, err := exec.LookPath("cgr-engine")
if err != nil {
return errors.New("Cannot find cgr-engine executable")
}
stopEngine()
engine := exec.Command(enginePath, "-cdrs", "-config", cfgPath)
engine := exec.Command(enginePath, "-config", cfgPath)
if err := engine.Start(); err != nil {
return fmt.Errorf("Cannot start cgr-engine: %s", err.Error())
}
@@ -102,7 +107,7 @@ func TestEmptyTables(t *testing.T) {
} else {
mysql = d.(*engine.MySQLStorage)
}
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
t.Fatal("Error on mysql creation: ", err.Error())
return // No point in going further
@@ -126,6 +131,12 @@ func TestCreateCdrFiles(t *testing.T) {
if err := os.MkdirAll(cfg.CdrcCdrInDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cfg.CdrcCdrInDir, err)
}
if err := os.RemoveAll(cfg.CdrcCdrOutDir); err != nil {
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
}
if err := os.MkdirAll(cfg.CdrcCdrOutDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cfg.CdrcCdrOutDir, err)
}
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file1.csv"), []byte(fileContent1), 0644); err != nil {
t.Fatal(err.Error)
}
@@ -144,7 +155,45 @@ func TestProcessCdrDir(t *testing.T) {
if err := startEngine(); err != nil {
t.Fatal(err.Error())
}
cdrc, err := NewCdrc(cfg, nil)
cdrc, err := NewCdrc(cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, cfg.CdrcCsvSep,
cfg.CdrcCdrFields, nil)
if err != nil {
t.Fatal(err.Error())
}
if err := cdrc.processCdrDir(); err != nil {
t.Error(err)
}
stopEngine()
}
// Creates cdr files and starts the engine
func TestCreateCdr3File(t *testing.T) {
if !*testLocal {
return
}
if err := os.RemoveAll(cfg.CdrcCdrInDir); err != nil {
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
}
if err := os.MkdirAll(cfg.CdrcCdrInDir, 0755); err != nil {
t.Fatal("Error creating folder: ", cfg.CdrcCdrInDir, err)
}
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file3.csv"), []byte(fileContent3), 0644); err != nil {
t.Fatal(err.Error)
}
}
func TestProcessCdr3Dir(t *testing.T) {
if !*testLocal {
return
}
if cfg.CdrcCdrs == utils.INTERNAL { // For now we only test over network
return
}
if err := startEngine(); err != nil {
t.Fatal(err.Error())
}
cdrc, err := NewCdrc(cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, ";",
cfg.CdrcCdrFields, nil)
if err != nil {
t.Fatal(err.Error())
}

View File

@@ -19,90 +19,173 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package cdrc
import (
//"bytes"
//"encoding/csv"
//"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
//"io"
"reflect"
"testing"
"time"
"unicode/utf8"
)
func TestParseFieldsConfig(t *testing.T) {
// Test default config
func TestRecordForkCdr(t *testing.T) {
cgrConfig, _ := config.NewDefaultCGRConfig()
// Test primary field index definition
cgrConfig.CdrcAccIdField = "detect_me"
cdrc := &Cdrc{cgrCfg: cgrConfig}
if err := cdrc.parseFieldsConfig(); err == nil {
t.Error("Failed detecting error in accounting id definition", err)
}
cgrConfig.CdrcAccIdField = "^static_val"
cgrConfig.CdrcSubjectField = "1"
cdrc = &Cdrc{cgrCfg: cgrConfig}
if err := cdrc.parseFieldsConfig(); err != nil {
t.Error("Failed to corectly parse primary fields %v", cdrc.cfgCdrFields)
}
cgrConfig.CdrcExtraFields = []string{"^static_val:orig_ip"}
// Test extra field index definition
cgrConfig.CdrcAccIdField = "0" // Put back as int
cgrConfig.CdrcExtraFields = []string{"supplier1", "orig_ip:11"}
cdrc = &Cdrc{cgrCfg: cgrConfig}
if err := cdrc.parseFieldsConfig(); err == nil {
t.Error("Failed detecting error in extra fields definition", err)
}
cgrConfig.CdrcExtraFields = []string{"supplier1:^top_supplier", "orig_ip:11"}
cdrc = &Cdrc{cgrCfg: cgrConfig}
if err := cdrc.parseFieldsConfig(); err != nil {
t.Errorf("Failed to corectly parse extra fields %v", cdrc.cfgCdrFields)
}
}
func TestRecordAsStoredCdr(t *testing.T) {
cgrConfig, _ := config.NewDefaultCGRConfig()
cgrConfig.CdrcExtraFields = []string{"supplier:11"}
cdrc := &Cdrc{cgrCfg: cgrConfig}
if err := cdrc.parseFieldsConfig(); err != nil {
t.Error("Failed parsing default fieldIndexesFromConfig", err)
}
cgrConfig.CdrcCdrFields["supplier"] = []*utils.RSRField{&utils.RSRField{Id: "14"}}
csvSepRune, _ := utf8.DecodeRune([]byte(cgrConfig.CdrcCsvSep))
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, csvSepRune,
cgrConfig.CdrcCdrFields, new(engine.CDRS), nil}
cdrRow := []string{"firstField", "secondField"}
_, err := cdrc.recordAsStoredCdr(cdrRow)
_, err := cdrc.recordToStoredCdr(cdrRow)
if err == nil {
t.Error("Failed to corectly detect missing fields from record")
}
cdrRow = []string{"acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963", "2013-02-03 19:50:00", "2013-02-03 19:54:00", "62",
"supplier1", "172.16.1.1"}
rtCdr, err := cdrc.recordAsStoredCdr(cdrRow)
cdrRow = []string{"ignored", "ignored", utils.VOICE, "acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963",
"2013-02-03 19:50:00", "2013-02-03 19:54:00", "62000000000", "supplier1", "172.16.1.1"}
rtCdr, err := cdrc.recordToStoredCdr(cdrRow)
if err != nil {
t.Error("Failed to parse CDR in rated cdr", err)
}
expectedCdr := &utils.StoredCdr{
CgrId: utils.FSCgrId(cdrRow[0]),
AccId: cdrRow[0],
CgrId: utils.Sha1(cdrRow[3], time.Date(2013, 2, 3, 19, 50, 0, 0, time.UTC).String()),
TOR: cdrRow[2],
AccId: cdrRow[3],
CdrHost: "0.0.0.0", // Got it over internal interface
CdrSource: cgrConfig.CdrcSourceId,
ReqType: cdrRow[1],
Direction: cdrRow[2],
Tenant: cdrRow[3],
TOR: cdrRow[4],
Account: cdrRow[5],
Subject: cdrRow[6],
Destination: cdrRow[7],
ReqType: cdrRow[4],
Direction: cdrRow[5],
Tenant: cdrRow[6],
Category: cdrRow[7],
Account: cdrRow[8],
Subject: cdrRow[9],
Destination: cdrRow[10],
SetupTime: time.Date(2013, 2, 3, 19, 50, 0, 0, time.UTC),
AnswerTime: time.Date(2013, 2, 3, 19, 54, 0, 0, time.UTC),
Duration: time.Duration(62) * time.Second,
Usage: time.Duration(62) * time.Second,
ExtraFields: map[string]string{"supplier": "supplier1"},
Cost: -1,
}
if !reflect.DeepEqual(expectedCdr, rtCdr) {
t.Errorf("Expected: \n%v, \nreceived: \n%v", expectedCdr, rtCdr)
}
/*
if cdrAsForm.Get(utils.CDRSOURCE) != cgrConfig.CdrcSourceId {
t.Error("Unexpected cdrsource received", cdrAsForm.Get(utils.CDRSOURCE))
}
if cdrAsForm.Get(utils.REQTYPE) != "prepaid" {
t.Error("Unexpected CDR value received", cdrAsForm.Get(utils.REQTYPE))
}
if cdrAsForm.Get("supplier") != "supplier1" {
t.Error("Unexpected CDR value received", cdrAsForm.Get("supplier"))
}
*/
}
/*
func TestDnTdmCdrs(t *testing.T) {
tdmCdrs := `
49773280254,0049LN130676000285,N_IP_0676_00-Internet 0676 WRAP 13,02.07.2014 15:24:40,02.07.2014 15:24:40,1,25,Peak,0.000000,49DE13
49893252121,0049651515477,N_MO_MRAP_00-WRAP Mobile,02.07.2014 15:24:41,02.07.2014 15:24:41,1,8,Peak,0.003920,49651
49497361022,0049LM0409005226,N_MO_MTMB_00-RW-Mobile,02.07.2014 15:24:41,02.07.2014 15:24:41,1,43,Peak,0.021050,49MTMB
`
cgrConfig, _ := config.NewDefaultCGRConfig()
eCdrs := []*utils.StoredCdr{
&utils.StoredCdr{
CgrId: utils.Sha1("49773280254", time.Date(2014, 7, 2, 15, 24, 40, 0, time.UTC).String()),
TOR: utils.VOICE,
AccId: "49773280254",
CdrHost: "0.0.0.0",
CdrSource: cgrConfig.CdrcSourceId,
ReqType: "rated",
Direction: "*out",
Tenant: "sip.test.deanconnect.nl",
Category: "call",
Account: "+49773280254",
Subject: "+49773280254",
Destination: "+49676000285",
SetupTime: time.Date(2014, 7, 2, 15, 24, 40, 0, time.UTC),
AnswerTime: time.Date(2014, 7, 2, 15, 24, 40, 0, time.UTC),
Usage: time.Duration(25) * time.Second,
Cost: -1,
},
&utils.StoredCdr{
CgrId: utils.Sha1("49893252121", time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC).String()),
TOR: utils.VOICE,
AccId: "49893252121",
CdrHost: "0.0.0.0",
CdrSource: cgrConfig.CdrcSourceId,
ReqType: "rated",
Direction: "*out",
Tenant: "sip.test.deanconnect.nl",
Category: "call",
Account: "+49893252121",
Subject: "+49893252121",
Destination: "+49651515477",
SetupTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
AnswerTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
Usage: time.Duration(8) * time.Second,
Cost: -1,
},
&utils.StoredCdr{
CgrId: utils.Sha1("49497361022", time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC).String()),
TOR: utils.VOICE,
AccId: "49497361022",
CdrHost: "0.0.0.0",
CdrSource: cgrConfig.CdrcSourceId,
ReqType: "rated",
Direction: "*out",
Tenant: "sip.test.deanconnect.nl",
Category: "call",
Account: "+49497361022",
Subject: "+49497361022",
Destination: "+499005226",
SetupTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
AnswerTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
Usage: time.Duration(43) * time.Second,
Cost: -1,
},
}
torFld, _ := utils.NewRSRField("^*voice")
acntFld, _ := utils.NewRSRField(`~0:s/^([1-9]\d+)$/+$1/`)
reqTypeFld, _ := utils.NewRSRField("^rated")
dirFld, _ := utils.NewRSRField("^*out")
tenantFld, _ := utils.NewRSRField("^sip.test.deanconnect.nl")
categFld, _ := utils.NewRSRField("^call")
dstFld, _ := utils.NewRSRField(`~1:s/^00(\d+)(?:[a-zA-Z].{3})*0*([1-9]\d+)$/+$1$2/`)
usageFld, _ := utils.NewRSRField(`~6:s/^(\d+)$/${1}s/`)
cgrConfig.CdrcCdrFields = map[string]*utils.RSRField{
utils.TOR: torFld,
utils.ACCID: &utils.RSRField{Id: "0"},
utils.REQTYPE: reqTypeFld,
utils.DIRECTION: dirFld,
utils.TENANT: tenantFld,
utils.CATEGORY: categFld,
utils.ACCOUNT: acntFld,
utils.SUBJECT: acntFld,
utils.DESTINATION: dstFld,
utils.SETUP_TIME: &utils.RSRField{Id: "4"},
utils.ANSWER_TIME: &utils.RSRField{Id: "4"},
utils.USAGE: usageFld,
}
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, ',',
cgrConfig.CdrcCdrFields, new(cdrs.CDRS), nil}
cdrsContent := bytes.NewReader([]byte(tdmCdrs))
csvReader := csv.NewReader(cdrsContent)
cdrs := make([]*utils.StoredCdr, 0)
for {
cdrCsv, err := csvReader.Read()
if err != nil && err == io.EOF {
break // End of file
} else if err != nil {
t.Error("Unexpected error:", err)
}
if cdr, err := cdrc.recordToStoredCdr(cdrCsv); err != nil {
t.Error("Unexpected error: ", err)
} else {
cdrs = append(cdrs, cdr)
}
}
if !reflect.DeepEqual(eCdrs, cdrs) {
for _, ecdr := range eCdrs {
fmt.Printf("Cdr expected: %+v\n", ecdr)
}
for _, cdr := range cdrs {
fmt.Printf("Cdr processed: %+v\n", cdr)
}
t.Errorf("Expecting: %+v, received: %+v", eCdrs, cdrs)
}
}
*/

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,10 +19,496 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package cdre
import (
"encoding/csv"
"encoding/json"
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"io"
"os"
"strconv"
"strings"
"time"
)
type CdrWriter interface {
WriteCdr(cdr *utils.StoredCdr) string
Close()
const (
COST_DETAILS = "cost_details"
FILLER = "filler"
CONSTANT = "constant"
METATAG = "metatag"
CONCATENATED_CDRFIELD = "concatenated_cdrfield"
COMBIMED = "combimed"
DATETIME = "datetime"
HTTP_POST = "http_post"
META_EXPORTID = "export_id"
META_TIMENOW = "time_now"
META_FIRSTCDRATIME = "first_cdr_atime"
META_LASTCDRATIME = "last_cdr_atime"
META_NRCDRS = "cdrs_number"
META_DURCDRS = "cdrs_duration"
META_SMSUSAGE = "sms_usage"
META_DATAUSAGE = "data_usage"
META_COSTCDRS = "cdrs_cost"
META_MASKDESTINATION = "mask_destination"
META_FORMATCOST = "format_cost"
)
var err error
func NewCdrExporter(cdrs []*utils.StoredCdr, logDb engine.LogStorage, exportTpl *config.CdreConfig, cdrFormat string, fieldSeparator rune, exportId string,
dataUsageMultiplyFactor, costMultiplyFactor float64, costShiftDigits, roundDecimals, cgrPrecision int, maskDestId string, maskLen int, httpSkipTlsCheck bool) (*CdrExporter, error) {
if len(cdrs) == 0 { // Nothing to export
return nil, nil
}
cdre := &CdrExporter{
cdrs: cdrs,
logDb: logDb,
exportTemplate: exportTpl,
cdrFormat: cdrFormat,
fieldSeparator: fieldSeparator,
exportId: exportId,
dataUsageMultiplyFactor: dataUsageMultiplyFactor,
costMultiplyFactor: costMultiplyFactor,
costShiftDigits: costShiftDigits,
roundDecimals: roundDecimals,
cgrPrecision: cgrPrecision,
maskDestId: maskDestId,
httpSkipTlsCheck: httpSkipTlsCheck,
maskLen: maskLen,
negativeExports: make(map[string]string),
}
if err := cdre.processCdrs(); err != nil {
return nil, err
}
return cdre, nil
}
type CdrExporter struct {
cdrs []*utils.StoredCdr
logDb engine.LogStorage // Used to extract cost_details if these are requested
exportTemplate *config.CdreConfig
cdrFormat string // csv, fwv
fieldSeparator rune
exportId string // Unique identifier or this export
dataUsageMultiplyFactor, costMultiplyFactor float64
costShiftDigits, roundDecimals, cgrPrecision int
maskDestId string
maskLen int
httpSkipTlsCheck bool
header, trailer []string // Header and Trailer fields
content [][]string // Rows of cdr fields
firstCdrATime, lastCdrATime time.Time
numberOfRecords int
totalDuration, totalDataUsage, totalSmsUsage time.Duration
totalCost float64
firstExpOrderId, lastExpOrderId int64
positiveExports []string // CGRIds of successfully exported CDRs
negativeExports map[string]string // CgrIds of failed exports
}
// Return Json marshaled callCost attached to
// Keep it separately so we test only this part in local tests
func (cdre *CdrExporter) getCdrCostDetails(cgrId, runId string) (string, error) {
cc, err := cdre.logDb.GetCallCostLog(cgrId, "", runId)
if err != nil {
return "", err
} else if cc == nil {
return "", nil
}
ccJson, _ := json.Marshal(cc)
return string(ccJson), nil
}
func (cdre *CdrExporter) getCombimedCdrFieldVal(processedCdr *utils.StoredCdr, filterRule, fieldRule *utils.RSRField) (string, error) {
fltrPass, ftrPassValue := processedCdr.PassesFieldFilter(filterRule)
if !fltrPass {
return "", nil
}
for _, cdr := range cdre.cdrs {
if cdr.CgrId != processedCdr.CgrId {
continue // We only care about cdrs with same primary cdr behind
}
if cdr.FieldAsString(&utils.RSRField{Id: filterRule.Id}) == ftrPassValue {
return cdr.FieldAsString(fieldRule), nil
}
}
return "", nil
}
// Check if the destination should be masked in output
func (cdre *CdrExporter) maskedDestination(destination string) bool {
if len(cdre.maskDestId) != 0 && engine.CachedDestHasPrefix(cdre.maskDestId, destination) {
return true
}
return false
}
func (cdre *CdrExporter) getDateTimeFieldVal(cdr *utils.StoredCdr, fltrRl, fieldRl *utils.RSRField, layout string) (string, error) {
if fieldRl == nil {
return "", nil
}
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
}
if len(layout) == 0 {
layout = time.RFC3339
}
if dtFld, err := utils.ParseTimeDetectLayout(cdr.FieldAsString(fieldRl)); err != nil {
return "", err
} else {
return dtFld.Format(layout), nil
}
}
// Extracts the value specified by cfgHdr out of cdr
func (cdre *CdrExporter) cdrFieldValue(cdr *utils.StoredCdr, fltrRl, rsrFld *utils.RSRField, layout string) (string, error) {
if rsrFld == nil {
return "", nil
}
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
}
if len(layout) == 0 {
layout = time.RFC3339
}
var cdrVal string
switch rsrFld.Id {
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
if cdrVal, err = cdre.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
return "", err
}
case utils.COST:
cdrVal = cdr.FormatCost(cdre.costShiftDigits, cdre.roundDecimals)
case utils.USAGE:
cdrVal = cdr.FormatUsage(layout)
case utils.SETUP_TIME:
cdrVal = cdr.SetupTime.Format(layout)
case utils.ANSWER_TIME: // Format time based on layout
cdrVal = cdr.AnswerTime.Format(layout)
case utils.DESTINATION:
cdrVal = cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})
if cdre.maskLen != -1 && cdre.maskedDestination(cdrVal) {
cdrVal = MaskDestination(cdrVal, cdre.maskLen)
}
default:
cdrVal = cdr.FieldAsString(rsrFld)
}
return rsrFld.ParseValue(cdrVal), nil
}
// Handle various meta functions used in header/trailer
func (cdre *CdrExporter) metaHandler(tag, arg string) (string, error) {
switch tag {
case META_EXPORTID:
return cdre.exportId, nil
case META_TIMENOW:
return time.Now().Format(arg), nil
case META_FIRSTCDRATIME:
return cdre.firstCdrATime.Format(arg), nil
case META_LASTCDRATIME:
return cdre.lastCdrATime.Format(arg), nil
case META_NRCDRS:
return strconv.Itoa(cdre.numberOfRecords), nil
case META_DURCDRS:
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
emulatedCdr := &utils.StoredCdr{TOR: utils.VOICE, Usage: cdre.totalDuration}
return emulatedCdr.FormatUsage(arg), nil
case META_SMSUSAGE:
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
emulatedCdr := &utils.StoredCdr{TOR: utils.SMS, Usage: cdre.totalSmsUsage}
return emulatedCdr.FormatUsage(arg), nil
case META_DATAUSAGE:
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
emulatedCdr := &utils.StoredCdr{TOR: utils.DATA, Usage: cdre.totalDataUsage}
return emulatedCdr.FormatUsage(arg), nil
case META_COSTCDRS:
return strconv.FormatFloat(utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
case META_MASKDESTINATION:
if cdre.maskedDestination(arg) {
return "1", nil
}
return "0", nil
default:
return "", fmt.Errorf("Unsupported METATAG: %s", tag)
}
return "", nil
}
// Compose and cache the header
func (cdre *CdrExporter) composeHeader() error {
for _, cfgFld := range cdre.exportTemplate.HeaderFields {
var outVal string
switch cfgFld.Type {
case FILLER:
outVal = cfgFld.Value
cfgFld.Padding = "right"
case CONSTANT:
outVal = cfgFld.Value
case METATAG:
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error()))
return err
}
cdre.header = append(cdre.header, fmtOut)
}
return nil
}
// Compose and cache the trailer
func (cdre *CdrExporter) composeTrailer() error {
for _, cfgFld := range cdre.exportTemplate.TrailerFields {
var outVal string
switch cfgFld.Type {
case FILLER:
outVal = cfgFld.Value
cfgFld.Padding = "right"
case CONSTANT:
outVal = cfgFld.Value
case METATAG:
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error()))
return err
}
cdre.trailer = append(cdre.trailer, fmtOut)
}
return nil
}
// Write individual cdr into content buffer, build stats
func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error {
if cdr == nil || len(cdr.CgrId) == 0 { // We do not export empty CDRs
return nil
}
if cdre.dataUsageMultiplyFactor != 0.0 && cdr.TOR == utils.DATA {
cdr.UsageMultiply(cdre.dataUsageMultiplyFactor, cdre.cgrPrecision)
}
if cdre.costMultiplyFactor != 0.0 {
cdr.CostMultiply(cdre.costMultiplyFactor, cdre.cgrPrecision)
}
var err error
cdrRow := make([]string, len(cdre.exportTemplate.ContentFields))
for idx, cfgFld := range cdre.exportTemplate.ContentFields {
var outVal string
switch cfgFld.Type {
case FILLER:
outVal = cfgFld.Value
cfgFld.Padding = "right"
case CONSTANT:
outVal = cfgFld.Value
case utils.CDRFIELD:
outVal, err = cdre.cdrFieldValue(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField(), cfgFld.Layout)
case DATETIME:
outVal, err = cdre.getDateTimeFieldVal(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField(), cfgFld.Layout)
case HTTP_POST:
var outValByte []byte
if outValByte, err = utils.HttpJsonPost(cfgFld.Value, cdre.httpSkipTlsCheck, cdr); err == nil {
outVal = string(outValByte)
if len(outVal) == 0 && cfgFld.Mandatory {
err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Name)
}
}
case COMBIMED:
outVal, err = cdre.getCombimedCdrFieldVal(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField())
case CONCATENATED_CDRFIELD:
for _, fld := range strings.Split(cfgFld.Value, ",") {
if fldOut, err := cdre.cdrFieldValue(cdr, cfgFld.Filter, &utils.RSRField{Id: fld}, cfgFld.Layout); err != nil {
break // The error will be reported bellow
} else {
outVal += fldOut
}
}
case METATAG:
if cfgFld.Value == META_MASKDESTINATION {
outVal, err = cdre.metaHandler(cfgFld.Value, cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION}))
} else {
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
}
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
return err
}
fmtOut := outVal
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CgrId, cdr.MediationRunId, cfgFld.Name, outVal, err.Error()))
return err
}
cdrRow[idx] += fmtOut
}
if len(cdrRow) == 0 { // No CDR data, most likely no configuration fields defined
return nil
} else {
cdre.content = append(cdre.content, cdrRow)
}
// Done with writing content, compute stats here
if cdre.firstCdrATime.IsZero() || cdr.AnswerTime.Before(cdre.firstCdrATime) {
cdre.firstCdrATime = cdr.AnswerTime
}
if cdr.AnswerTime.After(cdre.lastCdrATime) {
cdre.lastCdrATime = cdr.AnswerTime
}
cdre.numberOfRecords += 1
if cdr.TOR == utils.VOICE { // Only count duration for non data cdrs
cdre.totalDuration += cdr.Usage
}
if cdr.TOR == utils.SMS { // Count usage for SMS
cdre.totalSmsUsage += cdr.Usage
}
if cdr.TOR == utils.DATA { // Count usage for SMS
cdre.totalDataUsage += cdr.Usage
}
if cdr.Cost != -1 {
cdre.totalCost += cdr.Cost
cdre.totalCost = utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE)
}
if cdre.firstExpOrderId > cdr.OrderId || cdre.firstExpOrderId == 0 {
cdre.firstExpOrderId = cdr.OrderId
}
if cdre.lastExpOrderId < cdr.OrderId {
cdre.lastExpOrderId = cdr.OrderId
}
return nil
}
// Builds header, content and trailers
func (cdre *CdrExporter) processCdrs() error {
for _, cdr := range cdre.cdrs {
if err := cdre.processCdr(cdr); err != nil {
cdre.negativeExports[cdr.CgrId] = err.Error()
} else {
cdre.positiveExports = append(cdre.positiveExports, cdr.CgrId)
}
}
// Process header and trailer after processing cdrs since the metatag functions can access stats out of built cdrs
if cdre.exportTemplate.HeaderFields != nil {
if err := cdre.composeHeader(); err != nil {
return err
}
}
if cdre.exportTemplate.TrailerFields != nil {
if err := cdre.composeTrailer(); err != nil {
return err
}
}
return nil
}
// Simple write method
func (cdre *CdrExporter) writeOut(ioWriter io.Writer) error {
if len(cdre.header) != 0 {
for _, fld := range append(cdre.header, "\n") {
if _, err := io.WriteString(ioWriter, fld); err != nil {
return err
}
}
}
for _, cdrContent := range cdre.content {
for _, cdrFld := range append(cdrContent, "\n") {
if _, err := io.WriteString(ioWriter, cdrFld); err != nil {
return err
}
}
}
if len(cdre.trailer) != 0 {
for _, fld := range append(cdre.trailer, "\n") {
if _, err := io.WriteString(ioWriter, fld); err != nil {
return err
}
}
}
return nil
}
// csvWriter specific method
func (cdre *CdrExporter) writeCsv(csvWriter *csv.Writer) error {
csvWriter.Comma = cdre.fieldSeparator
if len(cdre.header) != 0 {
if err := csvWriter.Write(cdre.header); err != nil {
return err
}
}
for _, cdrContent := range cdre.content {
if err := csvWriter.Write(cdrContent); err != nil {
return err
}
}
if len(cdre.trailer) != 0 {
if err := csvWriter.Write(cdre.trailer); err != nil {
return err
}
}
csvWriter.Flush()
return nil
}
// General method to write the content out to a file
func (cdre *CdrExporter) WriteToFile(filePath string) error {
fileOut, err := os.Create(filePath)
if err != nil {
return err
}
defer fileOut.Close()
switch cdre.cdrFormat {
case utils.CDRE_DRYRUN:
return nil
case utils.CDRE_FIXED_WIDTH:
if err := cdre.writeOut(fileOut); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
case utils.CSV:
csvWriter := csv.NewWriter(fileOut)
if err := cdre.writeCsv(csvWriter); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
}
return nil
}
// Return the first exported Cdr OrderId
func (cdre *CdrExporter) FirstOrderId() int64 {
return cdre.firstExpOrderId
}
// Return the last exported Cdr OrderId
func (cdre *CdrExporter) LastOrderId() int64 {
return cdre.lastExpOrderId
}
// Return total cost in the exported cdrs
func (cdre *CdrExporter) TotalCost() float64 {
return cdre.totalCost
}
func (cdre *CdrExporter) TotalExportedCdrs() int {
return cdre.numberOfRecords
}
// Return successfully exported CgrIds
func (cdre *CdrExporter) PositiveExports() []string {
return cdre.positiveExports
}
// Return failed exported CgrIds together with the reason
func (cdre *CdrExporter) NegativeExports() map[string]string {
return cdre.negativeExports
}

113
cdre/cdrexporter_test.go Normal file
View File

@@ -0,0 +1,113 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package cdre
import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"testing"
"time"
)
func TestCdreGetCombimedCdrFieldVal(t *testing.T) {
logDb, _ := engine.NewMapStorage()
cfg, _ := config.NewDefaultCGRConfig()
cdrs := []*utils.StoredCdr{
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: "RUN_RTL", Cost: 1.01},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf2", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: "CUSTOMER1", Cost: 2.01},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: "CUSTOMER1", Cost: 3.01},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 4.01},
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1000", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: "RETAIL1", Cost: 5.01},
}
cdre, err := NewCdrExporter(cdrs, logDb, cfg.CdreDefaultInstance, cfg.CdreDefaultInstance.CdrFormat, cfg.CdreDefaultInstance.FieldSeparator,
"firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error("Unexpected error received: ", err)
}
fltrRule, _ := utils.NewRSRField("~mediation_runid:s/default/RUN_RTL/")
if costVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], fltrRule, &utils.RSRField{Id: "cost"}); err != nil {
t.Error(err)
} else if costVal != "1.01" {
t.Error("Expecting: 1.01, received: ", costVal)
}
fltrRule, _ = utils.NewRSRField("~mediation_runid:s/default/RETAIL1/")
if acntVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], fltrRule, &utils.RSRField{Id: "account"}); err != nil {
t.Error(err)
} else if acntVal != "1000" {
t.Error("Expecting: 1000, received: ", acntVal)
}
}
func TestGetDateTimeFieldVal(t *testing.T) {
cdreTst := new(CdrExporter)
cdrTst := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.01,
ExtraFields: map[string]string{"stop_time": "2014-06-11 19:19:00 +0000 UTC", "fieldextr2": "valextr2"}}
if cdrVal, err := cdreTst.getDateTimeFieldVal(cdrTst, nil, &utils.RSRField{Id: "stop_time"}, "2006-01-02 15:04:05"); err != nil {
t.Error(err)
} else if cdrVal != "2014-06-11 19:19:00" {
t.Error("Expecting: 2014-06-11 19:19:00, got: ", cdrVal)
}
// Test filter
fltrRule, _ := utils.NewRSRField("~tenant:s/(.+)/itsyscom.com/")
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, fltrRule, &utils.RSRField{Id: "stop_time"}, "2006-01-02 15:04:05"); err == nil {
t.Error(err)
}
// Test time parse error
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, nil, &utils.RSRField{Id: "fieldextr2"}, "2006-01-02 15:04:05"); err == nil {
t.Error("Should give error here, got none.")
}
}
func TestCdreCdrFieldValue(t *testing.T) {
cdre := new(CdrExporter)
cdr := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.01}
fltrRule, _ := utils.NewRSRField("~tenant:s/(.+)/cgrates.org/")
if val, err := cdre.cdrFieldValue(cdr, fltrRule, &utils.RSRField{Id: "destination"}, ""); err != nil {
t.Error(err)
} else if val != cdr.Destination {
t.Errorf("Expecting: %s, received: %s", cdr.Destination, val)
}
fltrRule, _ = utils.NewRSRField("~tenant:s/(.+)/itsyscom.com/")
if _, err := cdre.cdrFieldValue(cdr, fltrRule, &utils.RSRField{Id: "destination"}, ""); err == nil {
t.Error("Failed to use filter")
}
}

View File

@@ -1,53 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package cdre
import (
"encoding/csv"
"github.com/cgrates/cgrates/utils"
"io"
)
type CsvCdrWriter struct {
writer *csv.Writer
roundDecimals int // Round floats like Cost using this number of decimals
exportedFields []*utils.RSRField // The fields exported, order important
}
func NewCsvCdrWriter(writer io.Writer, roundDecimals int, exportedFields []*utils.RSRField) *CsvCdrWriter {
return &CsvCdrWriter{csv.NewWriter(writer), roundDecimals, exportedFields}
}
func (csvwr *CsvCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
row := make([]string, len(csvwr.exportedFields))
for idx, fld := range csvwr.exportedFields {
var fldVal string
if fld.Id == utils.COST {
fldVal = cdr.FormatCost(csvwr.roundDecimals)
} else {
fldVal = cdr.ExportFieldValue(fld.Id)
}
row[idx] = fld.ParseValue(fldVal)
}
return csvwr.writer.Write(row)
}
func (csvwr *CsvCdrWriter) Close() {
csvwr.writer.Flush()
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,7 +20,9 @@ package cdre
import (
"bytes"
"encoding/csv"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"strings"
"testing"
@@ -30,18 +32,55 @@ import (
func TestCsvCdrWriter(t *testing.T) {
writer := &bytes.Buffer{}
cfg, _ := config.NewDefaultCGRConfig()
exportedFields := append(cfg.CdreExportedFields, &utils.RSRField{Id: "extra3"}, &utils.RSRField{Id: "dummy_extra"}, &utils.RSRField{Id: "extra1"})
csvCdrWriter := NewCsvCdrWriter(writer, 4, exportedFields)
ratedCdr := &utils.StoredCdr{CgrId: utils.FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
logDb, _ := engine.NewMapStorage()
storedCdr1 := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01,
}
csvCdrWriter.WriteCdr(ratedCdr)
csvCdrWriter.Close()
expected := `b18944ef4dc618569f24c27b9872827a242bad0c,default,dsafdsaf,192.168.1.1,rated,*out,cgrates.org,call,1001,1001,1002,2013-11-07 08:42:25 +0000 UTC,2013-11-07 08:42:26 +0000 UTC,10,1.0100,val_extra3,"",val_extra1`
cdre, err := NewCdrExporter([]*utils.StoredCdr{storedCdr1}, logDb, cfg.CdreDefaultInstance, utils.CSV, ',', "firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error("Unexpected error received: ", err)
}
csvWriter := csv.NewWriter(writer)
if err := cdre.writeCsv(csvWriter); err != nil {
t.Error("Unexpected error: ", err)
}
expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6,default,*voice,dsafdsaf,rated,*out,cgrates.org,call,1001,1001,1002,2013-11-07T08:42:25Z,2013-11-07T08:42:26Z,10000000000,1.0100`
result := strings.TrimSpace(writer.String())
if result != expected {
t.Errorf("Expected: \n%s received: \n%s.", expected, result)
}
if cdre.TotalCost() != 1.01 {
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
}
}
func TestAlternativeFieldSeparator(t *testing.T) {
writer := &bytes.Buffer{}
cfg, _ := config.NewDefaultCGRConfig()
logDb, _ := engine.NewMapStorage()
storedCdr1 := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01,
}
cdre, err := NewCdrExporter([]*utils.StoredCdr{storedCdr1}, logDb, cfg.CdreDefaultInstance, utils.CSV, '|', "firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error("Unexpected error received: ", err)
}
csvWriter := csv.NewWriter(writer)
if err := cdre.writeCsv(csvWriter); err != nil {
t.Error("Unexpected error: ", err)
}
expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6|default|*voice|dsafdsaf|rated|*out|cgrates.org|call|1001|1001|1002|2013-11-07T08:42:25Z|2013-11-07T08:42:26Z|10000000000|1.0100`
result := strings.TrimSpace(writer.String())
if result != expected {
t.Errorf("Expected: \n%s received: \n%s.", expected, result)
}
if cdre.TotalCost() != 1.01 {
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
}
}

View File

@@ -1,267 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package cdre
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"io"
"os"
"strconv"
"strings"
"time"
)
const (
COST_DETAILS = "cost_details"
FILLER = "filler"
CONSTANT = "constant"
CDRFIELD = "cdrfield"
METATAG = "metatag"
CONCATENATED_CDRFIELD = "concatenated_cdrfield"
META_EXPORTID = "export_id"
META_TIMENOW = "time_now"
META_FIRSTCDRTIME = "first_cdr_time"
META_LASTCDRTIME = "last_cdr_time"
META_NRCDRS = "cdrs_number"
META_DURCDRS = "cdrs_duration"
META_COSTCDRS = "cdrs_cost"
)
var err error
func NewFWCdrWriter(logDb engine.LogStorage, outFile *os.File, exportTpl *config.CgrXmlCdreFwCfg, exportId string, roundDecimals int) (*FixedWidthCdrWriter, error) {
return &FixedWidthCdrWriter{
logDb: logDb,
writer: outFile,
exportTemplate: exportTpl,
exportId: exportId,
roundDecimals: roundDecimals,
header: &bytes.Buffer{},
content: &bytes.Buffer{},
trailer: &bytes.Buffer{}}, nil
}
type FixedWidthCdrWriter struct {
logDb engine.LogStorage // Used to extract cost_details if these are requested
writer io.Writer
exportTemplate *config.CgrXmlCdreFwCfg
exportId string // Unique identifier or this export
roundDecimals int
header, content, trailer *bytes.Buffer
firstCdrTime, lastCdrTime time.Time
numberOfRecords int
totalDuration time.Duration
totalCost float64
}
// Return Json marshaled callCost attached to
// Keep it separately so we test only this part in local tests
func (fww *FixedWidthCdrWriter) getCdrCostDetails(cgrId, runId string) (string, error) {
cc, err := fww.logDb.GetCallCostLog(cgrId, "", runId)
if err != nil {
return "", err
} else if cc == nil {
return "", nil
}
ccJson, _ := json.Marshal(cc)
return string(ccJson), nil
}
// Extracts the value specified by cfgHdr out of cdr
func (fww *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layout string) (string, error) {
rsrField, err := utils.NewRSRField(cfgHdr)
if err != nil {
return "", err
} else if rsrField == nil {
return "", nil
}
var cdrVal string
switch rsrField.Id {
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
if cdrVal, err = fww.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
return "", err
}
case utils.COST:
cdrVal = cdr.FormatCost(fww.roundDecimals)
case utils.SETUP_TIME:
cdrVal = cdr.SetupTime.Format(layout)
case utils.ANSWER_TIME: // Format time based on layout
cdrVal = cdr.AnswerTime.Format(layout)
default:
cdrVal = cdr.ExportFieldValue(rsrField.Id)
}
return rsrField.ParseValue(cdrVal), nil
}
func (fww *FixedWidthCdrWriter) metaHandler(tag, layout string) (string, error) {
switch tag {
case META_EXPORTID:
return fww.exportId, nil
case META_TIMENOW:
return time.Now().Format(layout), nil
case META_FIRSTCDRTIME:
return fww.firstCdrTime.Format(layout), nil
case META_LASTCDRTIME:
return fww.lastCdrTime.Format(layout), nil
case META_NRCDRS:
return strconv.Itoa(fww.numberOfRecords), nil
case META_DURCDRS:
return strconv.FormatFloat(fww.totalDuration.Seconds(), 'f', -1, 64), nil
case META_COSTCDRS:
return strconv.FormatFloat(utils.Round(fww.totalCost, fww.roundDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
default:
return "", errors.New("Unsupported METATAG")
}
return "", nil
}
// Writes the header into it's buffer
func (fww *FixedWidthCdrWriter) ComposeHeader() error {
header := ""
for _, cfgFld := range fww.exportTemplate.Header.Fields {
var outVal string
switch cfgFld.Type {
case FILLER, CONSTANT:
outVal = cfgFld.Value
case METATAG:
outVal, err = fww.metaHandler(cfgFld.Value, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, error: %s", err.Error()))
return err
}
if fmtOut, err := FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, error: %s", err.Error()))
return err
} else {
header += fmtOut
}
}
if len(header) == 0 { // No header data, most likely no configuration fields defined
return nil
}
header += "\n" // Done with cdr, postpend new line char
fww.header.WriteString(header)
return nil
}
// Writes the trailer into it's buffer
func (fww *FixedWidthCdrWriter) ComposeTrailer() error {
trailer := ""
for _, cfgFld := range fww.exportTemplate.Trailer.Fields {
var outVal string
switch cfgFld.Type {
case FILLER, CONSTANT:
outVal = cfgFld.Value
case METATAG:
outVal, err = fww.metaHandler(cfgFld.Value, cfgFld.Layout)
default:
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, error: %s", err.Error()))
return err
}
if fmtOut, err := FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, error: %s", err.Error()))
return err
} else {
trailer += fmtOut
}
}
if len(trailer) == 0 { // No header data, most likely no configuration fields defined
return nil
}
trailer += "\n" // Done with cdr, postpend new line char
fww.trailer.WriteString(trailer)
return nil
}
// Write individual cdr into content buffer, build stats
func (fww *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
if cdr == nil || len(cdr.CgrId) == 0 { // We do not export empty CDRs
return nil
}
var err error
cdrRow := ""
for _, cfgFld := range fww.exportTemplate.Content.Fields {
var outVal string
switch cfgFld.Type {
case FILLER, CONSTANT:
outVal = cfgFld.Value
case CDRFIELD:
outVal, err = fww.cdrFieldValue(cdr, cfgFld.Value, cfgFld.Layout)
case CONCATENATED_CDRFIELD:
for _, fld := range strings.Split(cfgFld.Value, ",") {
if fldOut, err := fww.cdrFieldValue(cdr, fld, cfgFld.Layout); err != nil {
break // The error will be reported bellow
} else {
outVal += fldOut
}
}
}
if err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
return err
}
if fmtOut, err := FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
return err
} else {
cdrRow += fmtOut
}
}
if len(cdrRow) == 0 { // No CDR data, most likely no configuration fields defined
return nil
}
cdrRow += "\n" // Done with cdr, postpend new line char
fww.content.WriteString(cdrRow)
// Done with writing content, compute stats here
if fww.firstCdrTime.IsZero() || cdr.SetupTime.Before(fww.firstCdrTime) {
fww.firstCdrTime = cdr.SetupTime
}
if cdr.SetupTime.After(fww.lastCdrTime) {
fww.lastCdrTime = cdr.SetupTime
}
fww.numberOfRecords += 1
fww.totalDuration += cdr.Duration
fww.totalCost += cdr.Cost
fww.totalCost = utils.Round(fww.totalCost, fww.roundDecimals, utils.ROUNDING_MIDDLE)
return nil
}
func (fww *FixedWidthCdrWriter) Close() {
if fww.exportTemplate.Header != nil {
fww.ComposeHeader()
}
if fww.exportTemplate.Trailer != nil {
fww.ComposeTrailer()
}
for _, buf := range []*bytes.Buffer{fww.header, fww.content, fww.trailer} {
fww.writer.Write(buf.Bytes())
}
}

View File

@@ -21,6 +21,7 @@ package cdre
import (
"bytes"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"math"
"testing"
@@ -29,82 +30,81 @@ import (
var hdrCfgFlds = []*config.CgrXmlCfgCdrField{
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "10", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3},
&config.CgrXmlCfgCdrField{Name: "DistributorCode", Type: CONSTANT, Value: "VOI", Width: 3},
&config.CgrXmlCfgCdrField{Name: "FileSeqNr", Type: METATAG, Value: "export_id", Width: 5, Strip: "right", Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "LastCdr", Type: METATAG, Value: "last_cdr_time", Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "LastCdr", Type: METATAG, Value: META_LASTCDRATIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "FileCreationfTime", Type: METATAG, Value: "time_now", Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "FileVersion", Type: CONSTANT, Value: "01", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 105, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 105},
}
var contentCfgFlds = []*config.CgrXmlCfgCdrField{
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "20", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Account", Type: CDRFIELD, Value: utils.ACCOUNT, Width: 12, Strip: "left", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Subject", Type: CDRFIELD, Value: utils.SUBJECT, Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "CLI", Type: CDRFIELD, Value: "cli", Width: 15, Strip: "xright", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Destination", Type: CDRFIELD, Value: utils.DESTINATION, Width: 24, Strip: "xright", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Account", Type: utils.CDRFIELD, Value: utils.ACCOUNT, Width: 12, Strip: "left", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Subject", Type: utils.CDRFIELD, Value: utils.SUBJECT, Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "CLI", Type: utils.CDRFIELD, Value: "cli", Width: 15, Strip: "xright", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Destination", Type: utils.CDRFIELD, Value: utils.DESTINATION, Width: 24, Strip: "xright", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "TOR", Type: CONSTANT, Value: "02", Width: 2},
&config.CgrXmlCfgCdrField{Name: "SubtypeTOR", Type: CONSTANT, Value: "11", Width: 4, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "SetupTime", Type: CDRFIELD, Value: utils.SETUP_TIME, Width: 12, Strip: "right", Padding: "right", Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "Duration", Type: CDRFIELD, Value: utils.DURATION, Width: 6, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "DataVolume", Type: FILLER, Width: 6, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "SetupTime", Type: utils.CDRFIELD, Value: utils.SETUP_TIME, Width: 12, Strip: "right", Padding: "right", Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "Duration", Type: utils.CDRFIELD, Value: utils.USAGE, Width: 6, Strip: "right", Padding: "right", Layout: utils.SECONDS},
&config.CgrXmlCfgCdrField{Name: "DataVolume", Type: FILLER, Width: 6},
&config.CgrXmlCfgCdrField{Name: "TaxCode", Type: CONSTANT, Value: "1", Width: 1},
&config.CgrXmlCfgCdrField{Name: "OperatorCode", Type: CDRFIELD, Value: "opercode", Width: 2, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "ProductId", Type: CDRFIELD, Value: "productid", Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "OperatorCode", Type: utils.CDRFIELD, Value: "opercode", Width: 2, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "ProductId", Type: utils.CDRFIELD, Value: "productid", Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "NetworkId", Type: CONSTANT, Value: "3", Width: 1},
&config.CgrXmlCfgCdrField{Name: "CallId", Type: CDRFIELD, Value: utils.ACCID, Width: 16, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "CallId", Type: utils.CDRFIELD, Value: utils.ACCID, Width: 16, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
&config.CgrXmlCfgCdrField{Name: "TerminationCode", Type: CONCATENATED_CDRFIELD, Value: "operator,product", Width: 5, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Cost", Type: CDRFIELD, Value: utils.COST, Width: 9, Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: CDRFIELD, Value: "destination_privacy", Width: 1, Strip: "right", Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Cost", Type: utils.CDRFIELD, Value: utils.COST, Width: 9, Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: METATAG, Value: META_MASKDESTINATION, Width: 1},
}
var trailerCfgFlds = []*config.CgrXmlCfgCdrField{
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "90", Width: 2},
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3},
&config.CgrXmlCfgCdrField{Name: "DistributorCode", Type: CONSTANT, Value: "VOI", Width: 3},
&config.CgrXmlCfgCdrField{Name: "FileSeqNr", Type: METATAG, Value: META_EXPORTID, Width: 5, Strip: "right", Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "NumberOfRecords", Type: METATAG, Value: META_NRCDRS, Width: 6, Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "CdrsDuration", Type: METATAG, Value: META_DURCDRS, Width: 8, Padding: "zeroleft"},
&config.CgrXmlCfgCdrField{Name: "FirstCdrTime", Type: METATAG, Value: META_FIRSTCDRTIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "LastCdrTime", Type: METATAG, Value: META_LASTCDRTIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 93, Padding: "right"},
&config.CgrXmlCfgCdrField{Name: "CdrsDuration", Type: METATAG, Value: META_DURCDRS, Width: 8, Padding: "zeroleft", Layout: "seconds"},
&config.CgrXmlCfgCdrField{Name: "FirstCdrTime", Type: METATAG, Value: META_FIRSTCDRATIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "LastCdrTime", Type: METATAG, Value: META_LASTCDRATIME, Width: 12, Layout: "020106150400"},
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 93},
}
// Write one CDR and test it's results only for content buffer
func TestWriteCdr(t *testing.T) {
wrBuf := &bytes.Buffer{}
exportTpl := &config.CgrXmlCdreFwCfg{Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
logDb, _ := engine.NewMapStorage()
cfg, _ := config.NewDefaultCGRConfig()
fixedWidth := utils.CDRE_FIXED_WIDTH
exportTpl := &config.CgrXmlCdreCfg{
CdrFormat: &fixedWidth,
Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
}
fwWriter := FixedWidthCdrWriter{writer: wrBuf, exportTemplate: exportTpl, roundDecimals: 4, header: &bytes.Buffer{}, content: &bytes.Buffer{}, trailer: &bytes.Buffer{}}
cdr := &utils.StoredCdr{CgrId: utils.FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
TOR: "call", Account: "1001", Subject: "1001", Destination: "1002",
cdr := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
TOR: utils.VOICE, OrderId: 1, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
if err := fwWriter.WriteCdr(cdr); err != nil {
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error(err)
}
eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.3457 \n"
contentOut := fwWriter.content.String()
if len(contentOut) != 145 {
t.Error("Unexpected content length", len(contentOut))
} else if contentOut != eContentOut {
t.Errorf("Content out different than expected. Have <%s>, expecting: <%s>", contentOut, eContentOut)
}
eHeader := "10 VOI0000007111308420024031415390001 \n"
eTrailer := "90 VOI0000000000100000010071113084200071113084200 \n"
outBeforeWrite := ""
if wrBuf.String() != outBeforeWrite {
t.Errorf("Output buffer should be empty before write")
eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.34570\n"
eTrailer := "90 VOI0000000000100000010071113084260071113084200 \n"
if err := cdre.writeOut(wrBuf); err != nil {
t.Error(err)
}
fwWriter.Close()
allOut := wrBuf.String()
eAllOut := eHeader + eContentOut + eTrailer
if math.Mod(float64(len(allOut)), 145) != 0 {
@@ -113,78 +113,98 @@ func TestWriteCdr(t *testing.T) {
t.Errorf("Output does not match expected length. Have output %q, expecting: %q", allOut, eAllOut)
}
// Test stats
if !fwWriter.firstCdrTime.Equal(cdr.SetupTime) {
t.Error("Unexpected firstCdrTime in stats: ", fwWriter.firstCdrTime)
} else if !fwWriter.lastCdrTime.Equal(cdr.SetupTime) {
t.Error("Unexpected lastCdrTime in stats: ", fwWriter.lastCdrTime)
} else if fwWriter.numberOfRecords != 1 {
t.Error("Unexpected number of records in the stats: ", fwWriter.numberOfRecords)
} else if fwWriter.totalDuration != cdr.Duration {
t.Error("Unexpected total duration in the stats: ", fwWriter.totalDuration)
} else if fwWriter.totalCost != utils.Round(cdr.Cost, fwWriter.roundDecimals, utils.ROUNDING_MIDDLE) {
t.Error("Unexpected total cost in the stats: ", fwWriter.totalCost)
if !cdre.firstCdrATime.Equal(cdr.AnswerTime) {
t.Error("Unexpected firstCdrATime in stats: ", cdre.firstCdrATime)
} else if !cdre.lastCdrATime.Equal(cdr.AnswerTime) {
t.Error("Unexpected lastCdrATime in stats: ", cdre.lastCdrATime)
} else if cdre.numberOfRecords != 1 {
t.Error("Unexpected number of records in the stats: ", cdre.numberOfRecords)
} else if cdre.totalDuration != cdr.Usage {
t.Error("Unexpected total duration in the stats: ", cdre.totalDuration)
} else if cdre.totalCost != utils.Round(cdr.Cost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) {
t.Error("Unexpected total cost in the stats: ", cdre.totalCost)
}
if cdre.FirstOrderId() != 1 {
t.Error("Unexpected FirstOrderId", cdre.FirstOrderId())
}
if cdre.LastOrderId() != 1 {
t.Error("Unexpected LastOrderId", cdre.LastOrderId())
}
if cdre.TotalCost() != utils.Round(cdr.Cost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) {
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
}
}
func TestWriteCdrs(t *testing.T) {
wrBuf := &bytes.Buffer{}
exportTpl := &config.CgrXmlCdreFwCfg{Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
logDb, _ := engine.NewMapStorage()
fixedWidth := utils.CDRE_FIXED_WIDTH
exportTpl := &config.CgrXmlCdreCfg{
CdrFormat: &fixedWidth,
Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
}
fwWriter := FixedWidthCdrWriter{writer: wrBuf, exportTemplate: exportTpl, roundDecimals: 4, header: &bytes.Buffer{}, content: &bytes.Buffer{}, trailer: &bytes.Buffer{}}
cdr1 := &utils.StoredCdr{CgrId: utils.FSCgrId("aaa1"), AccId: "aaa1", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
TOR: "call", Account: "1001", Subject: "1001", Destination: "1010",
cdr1 := &utils.StoredCdr{CgrId: utils.Sha1("aaa1", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
TOR: utils.VOICE, OrderId: 2, AccId: "aaa1", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1001", Subject: "1001", Destination: "1010",
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.25,
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.25,
ExtraFields: map[string]string{"productnumber": "12341", "fieldextr2": "valextr2"},
}
cdr2 := &utils.StoredCdr{CgrId: utils.FSCgrId("aaa2"), AccId: "aaa2", CdrHost: "192.168.1.2", ReqType: "prepaid", Direction: "*out", Tenant: "cgrates.org",
TOR: "call", Account: "1002", Subject: "1002", Destination: "1011",
cdr2 := &utils.StoredCdr{CgrId: utils.Sha1("aaa2", time.Date(2013, 11, 7, 7, 42, 20, 0, time.UTC).String()),
TOR: utils.VOICE, OrderId: 4, AccId: "aaa2", CdrHost: "192.168.1.2", ReqType: "prepaid", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1002", Subject: "1002", Destination: "1011",
SetupTime: time.Date(2013, 11, 7, 7, 42, 20, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 7, 42, 26, 0, time.UTC),
Duration: time.Duration(5) * time.Minute, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.40001,
Usage: time.Duration(5) * time.Minute, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.40001,
ExtraFields: map[string]string{"productnumber": "12342", "fieldextr2": "valextr2"},
}
cdr3 := &utils.StoredCdr{}
cdr4 := &utils.StoredCdr{CgrId: utils.FSCgrId("aaa3"), AccId: "aaa4", CdrHost: "192.168.1.4", ReqType: "postpaid", Direction: "*out", Tenant: "cgrates.org",
TOR: "call", Account: "1004", Subject: "1004", Destination: "1013",
cdr4 := &utils.StoredCdr{CgrId: utils.Sha1("aaa3", time.Date(2013, 11, 7, 9, 42, 18, 0, time.UTC).String()),
TOR: utils.VOICE, OrderId: 3, AccId: "aaa4", CdrHost: "192.168.1.4", ReqType: "postpaid", Direction: "*out", Tenant: "cgrates.org",
Category: "call", Account: "1004", Subject: "1004", Destination: "1013",
SetupTime: time.Date(2013, 11, 7, 9, 42, 18, 0, time.UTC),
AnswerTime: time.Date(2013, 11, 7, 9, 42, 26, 0, time.UTC),
Duration: time.Duration(20) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
Usage: time.Duration(20) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
ExtraFields: map[string]string{"productnumber": "12344", "fieldextr2": "valextr2"},
}
for _, cdr := range []*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4} {
if err := fwWriter.WriteCdr(cdr); err != nil {
t.Error(err)
}
contentOut := fwWriter.content.String()
if math.Mod(float64(len(contentOut)), 145) != 0 { // Rest must be 0 always, so content is always multiple of 145 which is our row fixLength
t.Error("Unexpected content length", len(contentOut))
}
cfg, _ := config.NewDefaultCGRConfig()
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',',
"fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
if err != nil {
t.Error(err)
}
if len(wrBuf.String()) != 0 {
t.Errorf("Output buffer should be empty before write")
if err := cdre.writeOut(wrBuf); err != nil {
t.Error(err)
}
fwWriter.Close()
if len(wrBuf.String()) != 725 {
t.Error("Output buffer does not contain expected info. Expecting len: 725, got: ", len(wrBuf.String()))
}
// Test stats
if !fwWriter.firstCdrTime.Equal(cdr2.SetupTime) {
t.Error("Unexpected firstCdrTime in stats: ", fwWriter.firstCdrTime)
if !cdre.firstCdrATime.Equal(cdr2.AnswerTime) {
t.Error("Unexpected firstCdrATime in stats: ", cdre.firstCdrATime)
}
if !fwWriter.lastCdrTime.Equal(cdr4.SetupTime) {
t.Error("Unexpected lastCdrTime in stats: ", fwWriter.lastCdrTime)
if !cdre.lastCdrATime.Equal(cdr4.AnswerTime) {
t.Error("Unexpected lastCdrATime in stats: ", cdre.lastCdrATime)
}
if fwWriter.numberOfRecords != 3 {
t.Error("Unexpected number of records in the stats: ", fwWriter.numberOfRecords)
if cdre.numberOfRecords != 3 {
t.Error("Unexpected number of records in the stats: ", cdre.numberOfRecords)
}
if fwWriter.totalDuration != time.Duration(330)*time.Second {
t.Error("Unexpected total duration in the stats: ", fwWriter.totalDuration)
if cdre.totalDuration != time.Duration(330)*time.Second {
t.Error("Unexpected total duration in the stats: ", cdre.totalDuration)
}
if fwWriter.totalCost != 5.9957 {
t.Error("Unexpected total cost in the stats: ", fwWriter.totalCost)
if cdre.totalCost != 5.9957 {
t.Error("Unexpected total cost in the stats: ", cdre.totalCost)
}
if cdre.FirstOrderId() != 2 {
t.Error("Unexpected FirstOrderId", cdre.FirstOrderId())
}
if cdre.LastOrderId() != 4 {
t.Error("Unexpected LastOrderId", cdre.LastOrderId())
}
if cdre.TotalCost() != 5.9957 {
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
}
}

View File

@@ -21,6 +21,7 @@ package cdre
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Used as generic function logic for various fields
@@ -34,6 +35,9 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo
if mandatory && len(source) == 0 {
return "", errors.New("Empty source value")
}
if width == 0 { // Disable width processing if not defined
return source, nil
}
if len(source) == width { // the source is exactly the maximum length
return source, nil
}
@@ -71,3 +75,18 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo
}
return source, nil
}
// Mask a number of characters in the suffix of the destination
func MaskDestination(dest string, maskLen int) string {
destLen := len(dest)
if maskLen < 0 {
return dest
} else if maskLen > destLen {
maskLen = destLen
}
dest = dest[:destLen-maskLen]
for i := 0; i < maskLen; i++ {
dest += utils.MASK_CHAR
}
return dest
}

View File

@@ -114,3 +114,20 @@ func TestPaddingNotAllowed(t *testing.T) {
t.Error("Expected error")
}
}
func TestMaskDestination(t *testing.T) {
dest := "+4986517174963"
if destMasked := MaskDestination(dest, 3); destMasked != "+4986517174***" {
t.Error("Unexpected mask applied", destMasked)
}
if destMasked := MaskDestination(dest, -1); destMasked != dest {
t.Error("Negative maskLen should not modify destination", destMasked)
}
if destMasked := MaskDestination(dest, 0); destMasked != dest {
t.Error("Zero maskLen should not modify destination", destMasked)
}
if destMasked := MaskDestination(dest, 100); destMasked != "**************" {
t.Error("High maskLen should return complete mask", destMasked)
}
}

View File

@@ -1,310 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package cdrs
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
const (
// Freswitch event property names
FS_CDR_MAP = "variables"
FS_DIRECTION = "direction"
FS_SUBJECT = "cgr_subject"
FS_ACCOUNT = "cgr_account"
FS_DESTINATION = "cgr_destination"
FS_REQTYPE = "cgr_reqtype" //prepaid or postpaid
FS_TOR = "cgr_tor"
FS_UUID = "uuid" // -Unique ID for this call leg
FS_CSTMID = "cgr_tenant"
FS_CALL_DEST_NR = "dialed_extension"
FS_PARK_TIME = "start_epoch"
FS_SETUP_TIME = "start_epoch"
FS_ANSWER_TIME = "answer_epoch"
FS_HANGUP_TIME = "end_epoch"
FS_DURATION = "billsec"
FS_USERNAME = "user_name"
FS_IP = "sip_local_network_addr"
FS_CDR_SOURCE = "freeswitch_json"
FS_SIP_REQUSER = "sip_req_user" // Apps like FusionPBX do not set dialed_extension, alternative being destination_number but that comes in customer profile, not in vars
)
type FSCdr struct {
vars map[string]string
body map[string]interface{} // keeps the loaded body for extra field search
}
func (fsCdr FSCdr) New(body []byte) (utils.RawCDR, error) {
fsCdr.vars = make(map[string]string)
var err error
if err = json.Unmarshal(body, &fsCdr.body); err == nil {
if variables, ok := fsCdr.body[FS_CDR_MAP]; ok {
if variables, ok := variables.(map[string]interface{}); ok {
for k, v := range variables {
fsCdr.vars[k] = v.(string)
}
}
return fsCdr, nil
}
}
return nil, err
}
func (fsCdr FSCdr) GetCgrId() string {
return utils.FSCgrId(fsCdr.vars[FS_UUID])
}
func (fsCdr FSCdr) GetAccId() string {
return fsCdr.vars[FS_UUID]
}
func (fsCdr FSCdr) GetCdrHost() string {
return fsCdr.vars[FS_IP]
}
func (fsCdr FSCdr) GetCdrSource() string {
return FS_CDR_SOURCE
}
func (fsCdr FSCdr) GetDirection() string {
//TODO: implement direction, not related to FS_DIRECTION but traffic towards or from subject/account
return "*out"
}
func (fsCdr FSCdr) GetSubject() string {
return utils.FirstNonEmpty(fsCdr.vars[FS_SUBJECT], fsCdr.vars[FS_USERNAME])
}
func (fsCdr FSCdr) GetAccount() string {
return utils.FirstNonEmpty(fsCdr.vars[FS_ACCOUNT], fsCdr.vars[FS_USERNAME])
}
// Charging destination number
func (fsCdr FSCdr) GetDestination() string {
return utils.FirstNonEmpty(fsCdr.vars[FS_DESTINATION], fsCdr.vars[FS_CALL_DEST_NR], fsCdr.vars[FS_SIP_REQUSER])
}
func (fsCdr FSCdr) GetTOR() string {
return utils.FirstNonEmpty(fsCdr.vars[FS_TOR], cfg.DefaultTOR)
}
func (fsCdr FSCdr) GetTenant() string {
return utils.FirstNonEmpty(fsCdr.vars[FS_CSTMID], cfg.DefaultTenant)
}
func (fsCdr FSCdr) GetReqType() string {
return utils.FirstNonEmpty(fsCdr.vars[FS_REQTYPE], cfg.DefaultReqType)
}
func (fsCdr FSCdr) GetExtraFields() map[string]string {
extraFields := make(map[string]string, len(cfg.CDRSExtraFields))
for _, field := range cfg.CDRSExtraFields {
origFieldVal, foundInVars := fsCdr.vars[field.Id]
if !foundInVars {
origFieldVal = fsCdr.searchExtraField(field.Id, fsCdr.body)
}
extraFields[field.Id] = field.ParseValue(origFieldVal)
}
return extraFields
}
func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) (result string) {
for key, value := range body {
switch v := value.(type) {
case string:
if key == field {
return v
}
case map[string]interface{}:
if result = fsCdr.searchExtraField(field, v); result != "" {
return
}
case []interface{}:
for _, item := range v {
if otherMap, ok := item.(map[string]interface{}); ok {
if result = fsCdr.searchExtraField(field, otherMap); result != "" {
return
}
} else {
engine.Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item)))
}
}
default:
engine.Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v)))
}
}
return
}
func (fsCdr FSCdr) GetSetupTime() (t time.Time, err error) {
//ToDo: Make sure we work with UTC instead of local time
at, err := strconv.ParseInt(fsCdr.vars[FS_SETUP_TIME], 0, 64)
t = time.Unix(at, 0)
return
}
func (fsCdr FSCdr) GetAnswerTime() (t time.Time, err error) {
//ToDo: Make sure we work with UTC instead of local time
at, err := strconv.ParseInt(fsCdr.vars[FS_ANSWER_TIME], 0, 64)
t = time.Unix(at, 0)
return
}
func (fsCdr FSCdr) GetHangupTime() (t time.Time, err error) {
hupt, err := strconv.ParseInt(fsCdr.vars[FS_HANGUP_TIME], 0, 64)
t = time.Unix(hupt, 0)
return
}
// Extracts duration as considered by the telecom switch
func (fsCdr FSCdr) GetDuration() time.Duration {
dur, _ := utils.ParseDurationWithSecs(fsCdr.vars[FS_DURATION])
return dur
}
func (fsCdr FSCdr) Store() (result string, err error) {
result += fsCdr.GetCgrId() + "|"
result += fsCdr.GetAccId() + "|"
result += fsCdr.GetCdrHost() + "|"
result += fsCdr.GetDirection() + "|"
result += fsCdr.GetSubject() + "|"
result += fsCdr.GetAccount() + "|"
result += fsCdr.GetDestination() + "|"
result += fsCdr.GetTOR() + "|"
result += fsCdr.GetAccId() + "|"
result += fsCdr.GetTenant() + "|"
result += fsCdr.GetReqType() + "|"
st, err := fsCdr.GetAnswerTime()
if err != nil {
return "", err
}
result += strconv.FormatInt(st.UnixNano(), 10) + "|"
et, err := fsCdr.GetHangupTime()
if err != nil {
return "", err
}
result += strconv.FormatInt(et.UnixNano(), 10) + "|"
result += strconv.FormatInt(int64(fsCdr.GetDuration().Seconds()), 10) + "|"
return
}
func (fsCdr FSCdr) Restore(input string) error {
return errors.New("Not implemented")
}
// Used in extra mediation
func (fsCdr FSCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) {
if utils.IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") {
return nil, errors.New(fmt.Sprintf("%s:FieldName", utils.ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory
}
var err error
var hasKey bool
var sTimeStr, aTimeStr, durStr string
rtCdr := new(utils.StoredCdr)
rtCdr.MediationRunId = runId
rtCdr.Cost = -1.0 // Default for non-rated CDR
if rtCdr.AccId = fsCdr.GetAccId(); len(rtCdr.AccId) == 0 {
if fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.ACCID))
} else { // Not mandatory, need to generate here CgrId
rtCdr.CgrId = utils.GenUUID()
}
} else { // hasKey, use it to generate cgrid
rtCdr.CgrId = utils.FSCgrId(rtCdr.AccId)
}
if rtCdr.CdrHost = fsCdr.GetCdrHost(); len(rtCdr.CdrHost) == 0 && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.CDRHOST))
}
if rtCdr.CdrSource = fsCdr.GetCdrSource(); len(rtCdr.CdrSource) == 0 && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.CDRSOURCE))
}
if strings.HasPrefix(reqTypeFld, utils.STATIC_VALUE_PREFIX) { // Values starting with prefix are not dynamically populated
rtCdr.ReqType = reqTypeFld[1:]
} else if rtCdr.ReqType, hasKey = fsCdr.vars[reqTypeFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, reqTypeFld))
}
if strings.HasPrefix(directionFld, utils.STATIC_VALUE_PREFIX) {
rtCdr.Direction = directionFld[1:]
} else if rtCdr.Direction, hasKey = fsCdr.vars[directionFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, directionFld))
}
if strings.HasPrefix(tenantFld, utils.STATIC_VALUE_PREFIX) {
rtCdr.Tenant = tenantFld[1:]
} else if rtCdr.Tenant, hasKey = fsCdr.vars[tenantFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, tenantFld))
}
if strings.HasPrefix(torFld, utils.STATIC_VALUE_PREFIX) {
rtCdr.TOR = torFld[1:]
} else if rtCdr.TOR, hasKey = fsCdr.vars[torFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, torFld))
}
if strings.HasPrefix(accountFld, utils.STATIC_VALUE_PREFIX) {
rtCdr.Account = accountFld[1:]
} else if rtCdr.Account, hasKey = fsCdr.vars[accountFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, accountFld))
}
if strings.HasPrefix(subjectFld, utils.STATIC_VALUE_PREFIX) {
rtCdr.Subject = subjectFld[1:]
} else if rtCdr.Subject, hasKey = fsCdr.vars[subjectFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, subjectFld))
}
if strings.HasPrefix(destFld, utils.STATIC_VALUE_PREFIX) {
rtCdr.Destination = destFld[1:]
} else if rtCdr.Destination, hasKey = fsCdr.vars[destFld]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, destFld))
}
if sTimeStr, hasKey = fsCdr.vars[setupTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, setupTimeFld))
} else {
if strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) {
sTimeStr = setupTimeFld[1:]
}
if rtCdr.SetupTime, err = utils.ParseTimeDetectLayout(sTimeStr); err != nil && fieldsMandatory {
return nil, err
}
}
if aTimeStr, hasKey = fsCdr.vars[answerTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, answerTimeFld))
} else {
if strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) {
aTimeStr = answerTimeFld[1:]
}
if rtCdr.AnswerTime, err = utils.ParseTimeDetectLayout(aTimeStr); err != nil && fieldsMandatory {
return nil, err
}
}
if durStr, hasKey = fsCdr.vars[durationFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(durationFld, utils.STATIC_VALUE_PREFIX) {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, durationFld))
} else {
if strings.HasPrefix(durationFld, utils.STATIC_VALUE_PREFIX) {
durStr = durationFld[1:]
}
if rtCdr.Duration, err = utils.ParseDurationWithSecs(durStr); err != nil && fieldsMandatory {
return nil, err
}
}
rtCdr.ExtraFields = make(map[string]string, len(extraFlds))
for _, fldName := range extraFlds {
if fldVal, hasKey := fsCdr.vars[fldName]; !hasKey && fieldsMandatory {
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, fldName))
} else {
rtCdr.ExtraFields[fldName] = fldVal
}
}
return rtCdr, nil
}

View File

@@ -22,29 +22,87 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/cgrates/cgrates/console"
"github.com/cgrates/cgrates/utils"
"io"
"log"
"net/rpc"
"net/rpc/jsonrpc"
"os"
"strings"
"github.com/cgrates/cgrates/console"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/liner"
)
var (
history_fn = os.Getenv("HOME") + "/.cgr_history"
version = flag.Bool("version", false, "Prints the application version.")
verbose = flag.Bool("verbose", false, "Show extra info about command execution.")
server = flag.String("server", "127.0.0.1:2012", "server address host:port")
rpc_encoding = flag.String("rpc_encoding", "json", "RPC encoding used <gob|json>")
client *rpc.Client
)
func executeCommand(command string) {
if strings.TrimSpace(command) == "" {
return
}
if strings.TrimSpace(command) == "help" {
commands := console.GetCommands()
fmt.Println("Commands:")
for name, cmd := range commands {
fmt.Print(name, cmd.Usage())
}
return
}
if strings.HasPrefix(command, "help") {
words := strings.Split(command, " ")
if len(words) > 1 {
commands := console.GetCommands()
if cmd, ok := commands[words[1]]; ok {
fmt.Print(cmd.Usage())
} else {
fmt.Print("Available commands: ")
for name, _ := range commands {
fmt.Print(name + " ")
}
fmt.Println()
}
return
}
}
cmd, cmdErr := console.GetCommandValue(command, *verbose)
if cmdErr != nil {
fmt.Println(cmdErr)
return
}
if cmd.RpcMethod() != "" {
res := cmd.RpcResult()
param := cmd.RpcParams()
//log.Print(reflect.TypeOf(param))
switch param.(type) {
case *console.StringWrapper:
param = param.(*console.StringWrapper).Item
}
//log.Printf("Param: %+v", param)
if rpcErr := client.Call(cmd.RpcMethod(), param, res); rpcErr != nil {
fmt.Println("Error executing command: " + rpcErr.Error())
} else {
result, _ := json.MarshalIndent(res, "", " ")
fmt.Println(string(result))
}
} else {
fmt.Println(cmd.LocalExecute())
}
}
func main() {
flag.Parse()
if *version {
fmt.Println("CGRateS " + utils.VERSION)
return
}
var client *rpc.Client
var err error
if *rpc_encoding == "json" {
client, err = jsonrpc.Dial("tcp", *server)
@@ -56,17 +114,69 @@ func main() {
log.Fatal("Could not connect to server " + *server)
}
defer client.Close()
// Strict command parsing starts here
args := append([]string{os.Args[0]}, flag.Args()...) // Emulate os.Args by prepending the cmd to list of args coming from flag
cmd, cmdErr := console.GetCommandValue(args)
if cmdErr != nil {
log.Fatal(cmdErr)
}
res := cmd.RpcResult()
if rpcErr := client.Call(cmd.RpcMethod(), cmd.RpcParams(), res); rpcErr != nil {
fmt.Println("Error executing command: " + rpcErr.Error())
}
result, _ := json.MarshalIndent(res, "", " ")
fmt.Println(string(result))
if len(flag.Args()) != 0 {
executeCommand(strings.Join(flag.Args(), " "))
return
}
fmt.Println("Welcome to CGRateS console!")
fmt.Println("Type `help` for a list of commands\n")
line := liner.NewLiner()
defer line.Close()
line.SetCompleter(func(line string) (comp []string) {
commands := console.GetCommands()
for name, cmd := range commands {
if strings.HasPrefix(name, strings.ToLower(line)) {
comp = append(comp, name)
}
// try arguments
if strings.HasPrefix(line, name) {
// get last word
lastSpace := strings.LastIndex(line, " ")
lastSpace += 1
for _, arg := range cmd.ClientArgs() {
if strings.HasPrefix(arg, line[lastSpace:]) {
comp = append(comp, line[:lastSpace]+arg)
}
}
}
}
return
})
if f, err := os.Open(history_fn); err == nil {
line.ReadHistory(f)
f.Close()
}
stop := false
for !stop {
if command, err := line.Prompt("cgr> "); err != nil {
if err == io.EOF {
fmt.Println("\nbye!")
stop = true
} else {
fmt.Print("Error reading line: ", err)
}
} else {
line.AppendHistory(command)
switch strings.ToLower(strings.TrimSpace(command)) {
case "quit", "exit", "bye", "close":
fmt.Println("\nbye!")
stop = true
default:
executeCommand(command)
}
}
}
if f, err := os.Create(history_fn); err != nil {
log.Print("Error writing history file: ", err)
} else {
line.WriteHistory(f)
f.Close()
}
}

View File

@@ -23,23 +23,21 @@ import (
"flag"
"fmt"
"log"
"net/rpc"
"os"
"runtime"
//"runtime"
"strconv"
"time"
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/balancer2go"
"github.com/cgrates/cgrates/cdrc"
"github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/history"
"github.com/cgrates/cgrates/mediator"
"github.com/cgrates/cgrates/scheduler"
"github.com/cgrates/cgrates/sessionmanager"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/rpcclient"
)
const (
@@ -51,6 +49,7 @@ const (
REDIS = "redis"
SAME = "same"
FS = "freeswitch"
OSIPS = "opensips"
)
var (
@@ -66,20 +65,21 @@ var (
exitChan = make(chan bool)
server = &engine.Server{}
scribeServer history.Scribe
cdrServer *cdrs.CDRS
cdrServer *engine.CDRS
cdrStats *engine.Stats
sm sessionmanager.SessionManager
medi *mediator.Mediator
medi *engine.Mediator
cfg *config.CGRConfig
err error
)
func cacheData(ratingDb engine.RatingStorage, accountDb engine.AccountingStorage, doneChan chan struct{}) {
if err := ratingDb.CacheRating(nil, nil, nil, nil); err != nil {
if err := ratingDb.CacheRating(nil, nil, nil, nil, nil); err != nil {
engine.Logger.Crit(fmt.Sprintf("Cache rating error: %s", err.Error()))
exitChan <- true
return
}
if err := accountDb.CacheAccounting(nil, nil, nil); err != nil {
if err := accountDb.CacheAccounting(nil, nil, nil, nil); err != nil {
engine.Logger.Crit(fmt.Sprintf("Cache accounting error: %s", err.Error()))
exitChan <- true
return
@@ -93,11 +93,11 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
<-cacheChan // Cache needs to come up before we are ready
connector = responder
} else {
var client *rpc.Client
var client *rpcclient.RpcClient
var err error
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.MediatorRater)
for i := 0; i < cfg.MediatorReconnects; i++ {
client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorReconnects, utils.GOB)
if err == nil { //Connected so no need to reiterate
break
}
@@ -111,23 +111,24 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
connector = &engine.RPCClientConnector{Client: client}
}
var err error
medi, err = mediator.NewMediator(connector, loggerDb, cdrDb, cfg)
medi, err = engine.NewMediator(connector, loggerDb, cdrDb, cdrStats, cfg)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Mediator config parsing error: %v", err))
exitChan <- true
return
}
engine.Logger.Info("Registering Mediator RPC service.")
server.RpcRegister(&mediator.MediatorV1{Medi: medi})
server.RpcRegister(&apier.MediatorV1{Medi: medi})
close(chanDone)
}
func startCdrc(cdrsChan chan struct{}) {
if cfg.CdrcCdrs == utils.INTERNAL {
// Fires up a cdrc instance
func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField) {
if cdrsAddress == utils.INTERNAL {
<-cdrsChan // Wait for CDRServer to come up before start processing
}
cdrc, err := cdrc.NewCdrc(cfg, cdrServer)
cdrc, err := cdrc.NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId, runDelay, csvSep, cdrFields, cdrServer)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Cdrc config parsing error: %s", err.Error()))
exitChan <- true
@@ -140,16 +141,15 @@ func startCdrc(cdrsChan chan struct{}) {
}
func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage, cacheChan chan struct{}) {
var connector engine.Connector
var raterConn, cdrsConn engine.Connector
var client *rpcclient.RpcClient
if cfg.SMRater == utils.INTERNAL {
<-cacheChan // Wait for the cache to init before start doing queries
connector = responder
raterConn = responder
} else {
var client *rpc.Client
var err error
for i := 0; i < cfg.SMRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.SMRater)
for i := 0; i < cfg.SMReconnects; i++ {
client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMReconnects, utils.GOB)
if err == nil { //Connected so no need to reiterate
break
}
@@ -159,18 +159,39 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage
engine.Logger.Crit(fmt.Sprintf("<SessionManager> Could not connect to engine: %v", err))
exitChan <- true
}
connector = &engine.RPCClientConnector{Client: client}
raterConn = &engine.RPCClientConnector{Client: client}
}
if cfg.SMCdrS == cfg.SMRater {
cdrsConn = raterConn
} else if cfg.SMCdrS == utils.INTERNAL {
<-cacheChan // Wait for the cache to init before start doing queries
cdrsConn = responder
} else if len(cfg.SMCdrS) != 0 {
for i := 0; i < cfg.SMReconnects; i++ {
client, err = rpcclient.NewRpcClient("tcp", cfg.SMCdrS, 0, cfg.SMReconnects, utils.GOB)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i+1) * time.Second)
}
if err != nil {
engine.Logger.Crit(fmt.Sprintf("<SM-OpenSIPS> Could not connect to CDRS via RPC: %v", err))
exitChan <- true
}
cdrsConn = &engine.RPCClientConnector{Client: client}
}
switch cfg.SMSwitchType {
case FS:
dp, _ := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval))
sm = sessionmanager.NewFSSessionManager(loggerDb, connector, dp)
errConn := sm.Connect(cfg)
if errConn != nil {
engine.Logger.Err(fmt.Sprintf("<SessionManager> error: %s!", errConn))
}
sm = sessionmanager.NewFSSessionManager(cfg, loggerDb, raterConn, cdrsConn, dp)
case OSIPS:
sm, _ = sessionmanager.NewOSipsSessionManager(cfg, raterConn, cdrsConn)
default:
engine.Logger.Err(fmt.Sprintf("<SessionManager> Unsupported session manger type: %s!", cfg.SMSwitchType))
exitChan <- true
}
if err = sm.Connect(); err != nil {
engine.Logger.Err(fmt.Sprintf("<SessionManager> error: %s!", err))
}
exitChan <- true
}
@@ -184,8 +205,11 @@ func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage, mediChan, d
return
}
}
cdrServer = cdrs.New(cdrDb, medi, cfg)
cdrServer = engine.NewCdrS(cdrDb, medi, cdrStats, cfg)
cdrServer.RegisterHanlersToServer(server)
engine.Logger.Info("Registering CDRS RPC service.")
server.RpcRegister(&apier.CDRSV1{CdrSrv: cdrServer})
responder.CdrSrv = cdrServer // Make the cdrserver available for internal communication
close(doneChan)
}
@@ -200,7 +224,7 @@ func startHistoryServer(chanDone chan struct{}) {
}
// chanStartServer will report when server is up, useful for internal requests
func startHistoryAgent(scribeServer history.Scribe, chanServerStarted chan struct{}) {
func startHistoryAgent(chanServerStarted chan struct{}) {
if cfg.HistoryServer == utils.INTERNAL { // For internal requests, wait for server to come online before connecting
engine.Logger.Crit(fmt.Sprintf("<HistoryAgent> Connecting internally to HistoryServer"))
select {
@@ -224,7 +248,7 @@ func startHistoryAgent(scribeServer history.Scribe, chanServerStarted chan struc
time.Sleep(time.Duration(i) * time.Second)
}
}
engine.SetHistoryScribe(scribeServer)
engine.SetHistoryScribe(scribeServer) // scribeServer comes from global variable
return
}
@@ -287,9 +311,9 @@ func main() {
if *pidFile != "" {
writePid()
}
runtime.GOMAXPROCS(runtime.NumCPU())
// runtime.GOMAXPROCS(runtime.NumCPU()) // For now it slows down computing due to CPU management, to be reviewed in future Go releases
cfg, err = config.NewCGRConfig(cfgPath)
cfg, err = config.NewCGRConfigFromFile(cfgPath)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Could not parse config: %s exiting!", err))
return
@@ -355,7 +379,7 @@ func main() {
loadDb = logDb.(engine.LoadStorage)
cdrDb = logDb.(engine.CdrStorage)
engine.SetRoundingMethodAndDecimals(cfg.RoundingMethod, cfg.RoundingDecimals)
engine.SetRoundingDecimals(cfg.RoundingDecimals)
if cfg.SMDebitInterval > 0 {
if dp, err := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval)); err == nil {
engine.SetDebitPeriod(dp)
@@ -382,13 +406,22 @@ func main() {
stopHandled = true
}
if cfg.CDRStatsEnabled { // Init it here so we make it availabe to the Apier
cdrStats = engine.NewStats(ratingDb)
if cfg.CDRStatConfig != nil && len(cfg.CDRStatConfig.Metrics) != 0 {
cdrStats.AddQueue(engine.NewCdrStatsFromCdrStatsCfg(cfg.CDRStatConfig), nil)
}
server.RpcRegister(cdrStats)
server.RpcRegister(&apier.CDRStatsV1{cdrStats}) // Public APIs
}
responder := &engine.Responder{ExitChan: exitChan}
apier := &apier.ApierV1{StorDb: loadDb, RatingDb: ratingDb, AccountDb: accountDb, CdrDb: cdrDb, LogDb: logDb, Config: cfg}
apierRpc := &apier.ApierV1{StorDb: loadDb, RatingDb: ratingDb, AccountDb: accountDb, CdrDb: cdrDb, LogDb: logDb, Config: cfg, Responder: responder, CdrStatsSrv: cdrStats}
if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterBalancer != utils.INTERNAL {
engine.Logger.Info("Registering Rater service")
server.RpcRegister(responder)
server.RpcRegister(apier)
server.RpcRegister(apierRpc)
}
if cfg.BalancerEnabled {
@@ -397,7 +430,7 @@ func main() {
stopHandled = true
responder.Bal = bal
server.RpcRegister(responder)
server.RpcRegister(apier)
server.RpcRegister(apierRpc)
if cfg.RaterEnabled {
engine.Logger.Info("<Balancer> Registering internal rater")
bal.AddClient("local", new(engine.ResponderWorker))
@@ -413,7 +446,7 @@ func main() {
go func() {
sched := scheduler.NewScheduler()
go reloadSchedulerSingnalHandler(sched, accountDb)
apier.Sched = sched
apierRpc.Sched = sched
sched.LoadActionTimings(accountDb)
sched.Loop()
}()
@@ -428,7 +461,7 @@ func main() {
if cfg.HistoryAgentEnabled {
engine.Logger.Info("Starting CGRateS History Agent.")
go startHistoryAgent(scribeServer, histServChan)
go startHistoryAgent(histServChan)
}
var medChan chan struct{}
@@ -452,10 +485,23 @@ func main() {
// close all sessions on shutdown
go shutdownSessionmanagerSingnalHandler()
}
if cfg.CdrcEnabled {
var cdrcEnabled bool
if cfg.CdrcEnabled { // Start default cdrc configured in csv here
cdrcEnabled = true
go startCdrc(cdrsChan, cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, cfg.CdrcCsvSep, cfg.CdrcCdrFields)
}
if cfg.XmlCfgDocument != nil {
for _, xmlCdrc := range cfg.XmlCfgDocument.GetCdrcCfgs("") {
if !xmlCdrc.Enabled {
continue
}
cdrcEnabled = true
go startCdrc(cdrsChan, xmlCdrc.CdrsAddress, xmlCdrc.CdrType, xmlCdrc.CdrInDir, xmlCdrc.CdrOutDir,
xmlCdrc.CdrSourceId, time.Duration(xmlCdrc.RunDelay), xmlCdrc.CsvSeparator, xmlCdrc.CdrRSRFields())
}
}
if cdrcEnabled {
engine.Logger.Info("Starting CGRateS CDR client.")
go startCdrc(cdrsChan)
}
// Start the servers

View File

@@ -125,8 +125,10 @@ func shutdownSessionmanagerSingnalHandler() {
signal.Notify(c, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
<-c
if err := sm.Shutdown(); err != nil {
engine.Logger.Warning(fmt.Sprintf("<SessionManager> %s", err))
if sm != nil {
if err := sm.Shutdown(); err != nil {
engine.Logger.Warning(fmt.Sprintf("<SessionManager> %s", err))
}
}
exitChan <- true
}

View File

@@ -57,18 +57,19 @@ var (
dbdata_encoding = flag.String("dbdata_encoding", cgrConfig.DBDataEncoding, "The encoding used to store object data in strings")
flush = flag.Bool("flushdb", false, "Flush the database before importing")
tpid = flag.String("tpid", "", "The tariff plan id from the database")
dataPath = flag.String("path", "./", "The path to folder containing the data files")
version = flag.Bool("version", false, "Prints the application version.")
verbose = flag.Bool("verbose", false, "Enable detailed verbose logging output")
dryRun = flag.Bool("dry_run", false, "When true will not save loaded data to dataDb but just parse it for consistency and errors.")
stats = flag.Bool("stats", false, "Generates statsistics about given data.")
fromStorDb = flag.Bool("from_stordb", false, "Load the tariff plan from storDb to dataDb")
toStorDb = flag.Bool("to_stordb", false, "Import the tariff plan from files to storDb")
historyServer = flag.String("history_server", cgrConfig.RPCGOBListen, "The history server address:port, empty to disable automaticautomatic history archiving")
raterAddress = flag.String("rater_address", cgrConfig.RPCGOBListen, "Rater service to contact for cache reloads, empty to disable automatic cache reloads")
runId = flag.String("runid", "", "Uniquely identify an import/load, postpended to some automatic fields")
flush = flag.Bool("flushdb", false, "Flush the database before importing")
tpid = flag.String("tpid", "", "The tariff plan id from the database")
dataPath = flag.String("path", "./", "The path to folder containing the data files")
version = flag.Bool("version", false, "Prints the application version.")
verbose = flag.Bool("verbose", false, "Enable detailed verbose logging output")
dryRun = flag.Bool("dry_run", false, "When true will not save loaded data to dataDb but just parse it for consistency and errors.")
stats = flag.Bool("stats", false, "Generates statsistics about given data.")
fromStorDb = flag.Bool("from_stordb", false, "Load the tariff plan from storDb to dataDb")
toStorDb = flag.Bool("to_stordb", false, "Import the tariff plan from files to storDb")
historyServer = flag.String("history_server", cgrConfig.RPCGOBListen, "The history server address:port, empty to disable automaticautomatic history archiving")
raterAddress = flag.String("rater_address", cgrConfig.RPCGOBListen, "Rater service to contact for cache reloads, empty to disable automatic cache reloads")
cdrstatsAddress = flag.String("cdrstats_address", cgrConfig.RPCGOBListen, "CDRStats service to contact for data reloads, empty to disable automatic data reloads")
runId = flag.String("runid", "", "Uniquely identify an import/load, postpended to some automatic fields")
)
func main() {
@@ -81,7 +82,7 @@ func main() {
var ratingDb engine.RatingStorage
var accountDb engine.AccountingStorage
var storDb engine.LoadStorage
var rater *rpc.Client
var rater, cdrstats *rpc.Client
var loader engine.TPLoader
// Init necessary db connections, only if not already
if !*dryRun { // make sure we do not need db connections on dry run, also not importing into any stordb
@@ -137,10 +138,13 @@ func main() {
path.Join(*dataPath, utils.RATING_PLANS_CSV),
path.Join(*dataPath, utils.RATING_PROFILES_CSV),
path.Join(*dataPath, utils.SHARED_GROUPS_CSV),
path.Join(*dataPath, utils.LCRS_CSV),
path.Join(*dataPath, utils.ACTIONS_CSV),
path.Join(*dataPath, utils.ACTION_PLANS_CSV),
path.Join(*dataPath, utils.ACTION_TRIGGERS_CSV),
path.Join(*dataPath, utils.ACCOUNT_ACTIONS_CSV))
path.Join(*dataPath, utils.ACCOUNT_ACTIONS_CSV),
path.Join(*dataPath, utils.DERIVED_CHARGERS_CSV),
path.Join(*dataPath, utils.CDR_STATS_CSV))
}
err = loader.LoadAll()
if err != nil {
@@ -172,6 +176,19 @@ func main() {
} else {
log.Print("WARNING: Rates automatic cache reloading is disabled!")
}
if *cdrstatsAddress != "" { // Init connection to rater so we can reload it's data
if *cdrstatsAddress == *raterAddress {
cdrstats = rater
} else {
cdrstats, err = rpc.Dial("tcp", *cdrstatsAddress)
if err != nil {
log.Fatalf("Could not connect to CDRStats API: %s", err.Error())
return
}
}
} else {
log.Print("WARNING: CDRStats automatic data reload is disabled!")
}
// write maps to database
if err := loader.WriteToDatabase(*flush, *verbose); err != nil {
@@ -190,12 +207,17 @@ func main() {
shgIds, _ := loader.GetLoadedIds(engine.SHARED_GROUP_PREFIX)
rpAliases, _ := loader.GetLoadedIds(engine.RP_ALIAS_PREFIX)
accAliases, _ := loader.GetLoadedIds(engine.ACC_ALIAS_PREFIX)
lcrIds, _ := loader.GetLoadedIds(engine.LCR_PREFIX)
dcs, _ := loader.GetLoadedIds(engine.DERIVEDCHARGERS_PREFIX)
// Reload cache first since actions could be calling info from within
if *verbose {
log.Print("Reloading cache")
}
if err = rater.Call("ApierV1.ReloadCache", utils.ApiReloadCache{dstIds, rplIds, rpfIds, actIds, shgIds, rpAliases, accAliases}, &reply); err != nil {
log.Fatalf("Got error on cache reload: %s", err.Error())
if *flush {
dstIds, rplIds, rpfIds, rpAliases, lcrIds = nil, nil, nil, nil, nil // Should reload all these on flush
}
if err = rater.Call("ApierV1.ReloadCache", utils.ApiReloadCache{dstIds, rplIds, rpfIds, actIds, shgIds, rpAliases, accAliases, lcrIds, dcs}, &reply); err != nil {
log.Printf("WARNING: Got error on cache reload: %s\n", err.Error())
}
actTmgIds, _ := loader.GetLoadedIds(engine.ACTION_TIMING_PREFIX)
if len(actTmgIds) != 0 {
@@ -203,9 +225,24 @@ func main() {
log.Print("Reloading scheduler")
}
if err = rater.Call("ApierV1.ReloadScheduler", "", &reply); err != nil {
log.Fatalf("Got error on scheduler reload: %s", err.Error())
log.Printf("WARNING: Got error on scheduler reload: %s\n", err.Error())
}
}
}
if cdrstats != nil {
statsQueueIds, _ := loader.GetLoadedIds(engine.CDR_STATS_PREFIX)
if *flush {
statsQueueIds = []string{} // Force reload all
}
if len(statsQueueIds) != 0 {
if *verbose {
log.Print("Reloading CDRStats data")
}
var reply string
if err := cdrstats.Call("CDRStatsV1.ReloadQueues", utils.AttrCDRStatsReloadQueues{StatsQueueIds: statsQueueIds}, &reply); err != nil {
log.Printf("WARNING: Failed reloading stat queues, error: %s\n", err.Error())
}
}
}
}

View File

@@ -30,6 +30,7 @@ import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
var (
@@ -52,10 +53,11 @@ var (
accountdb_pass = flag.String("accountdb_passwd", cgrConfig.AccountDBPass, "The AccountingDb user's password.")
dbdata_encoding = flag.String("dbdata_encoding", cgrConfig.DBDataEncoding, "The encoding used to store object data in strings.")
raterAddress = flag.String("rater_address", "", "Rater address for remote tests. Empty for internal rater.")
tor = flag.String("tor", "call", "The type of record to use in queries.")
tenant = flag.String("tenant", "call", "The type of record to use in queries.")
tor = flag.String("tor", utils.VOICE, "The type of record to use in queries.")
category = flag.String("category", "call", "The Record category to test.")
tenant = flag.String("tenant", "cgrates.org", "The type of record to use in queries.")
subject = flag.String("subject", "1001", "The rating subject to use in queries.")
destination = flag.String("destination", "+4986517174963", "The destination to use in queries.")
destination = flag.String("destination", "1002", "The destination to use in queries.")
nilDuration = time.Duration(0)
)
@@ -73,7 +75,7 @@ func durInternalRater(cd *engine.CallDescriptor) (time.Duration, error) {
}
defer accountDb.Close()
engine.SetAccountingStorage(accountDb)
if err := ratingDb.CacheRating(nil, nil, nil, nil); err != nil {
if err := ratingDb.CacheRating(nil, nil, nil, nil, nil); err != nil {
return nilDuration, fmt.Errorf("Cache rating error: %s", err.Error())
}
log.Printf("Runnning %d cycles...", *runs)
@@ -150,14 +152,15 @@ func main() {
defer pprof.StopCPUProfile()
}
cd := &engine.CallDescriptor{
TimeStart: time.Date(2013, time.December, 13, 22, 30, 0, 0, time.UTC),
TimeEnd: time.Date(2013, time.December, 13, 22, 31, 0, 0, time.UTC),
CallDuration: 60 * time.Second,
Direction: "*out",
TOR: *tor,
Tenant: *tenant,
Subject: *subject,
Destination: *destination,
TimeStart: time.Date(2014, time.December, 11, 55, 30, 0, 0, time.UTC),
TimeEnd: time.Date(2014, time.December, 11, 55, 31, 0, 0, time.UTC),
DurationIndex: 60 * time.Second,
Direction: "*out",
TOR: *tor,
Category: *category,
Tenant: *tenant,
Subject: *subject,
Destination: *destination,
}
var duration time.Duration
var err error

244
config/cdreconfig.go Normal file
View File

@@ -0,0 +1,244 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"errors"
"github.com/cgrates/cgrates/utils"
)
// Converts a list of field identifiers into proper CDR field content
func NewCdreCdrFieldsFromIds(withFixedWith bool, fldsIds ...string) ([]*CdreCdrField, error) {
cdrFields := make([]*CdreCdrField, len(fldsIds))
for idx, fldId := range fldsIds {
if parsedRsr, err := utils.NewRSRField(fldId); err != nil {
return nil, err
} else {
cdrFld := &CdreCdrField{Name: fldId, Type: utils.CDRFIELD, Value: fldId, valueAsRsrField: parsedRsr}
if err := cdrFld.setDefaultFieldProperties(withFixedWith); err != nil { // Set default fixed width properties to be used later if needed
return nil, err
}
cdrFields[idx] = cdrFld
}
}
return cdrFields, nil
}
func NewDefaultCdreConfig() (*CdreConfig, error) {
cdreCfg := new(CdreConfig)
if err := cdreCfg.setDefaults(); err != nil {
return nil, err
}
return cdreCfg, nil
}
// One instance of CdrExporter
type CdreConfig struct {
CdrFormat string
FieldSeparator rune
DataUsageMultiplyFactor float64
CostMultiplyFactor float64
CostRoundingDecimals int
CostShiftDigits int
MaskDestId string
MaskLength int
ExportDir string
HeaderFields []*CdreCdrField
ContentFields []*CdreCdrField
TrailerFields []*CdreCdrField
}
// Set here defaults
func (cdreCfg *CdreConfig) setDefaults() error {
cdreCfg.CdrFormat = utils.CSV
cdreCfg.FieldSeparator = utils.CSV_SEP
cdreCfg.DataUsageMultiplyFactor = 0.0
cdreCfg.CostMultiplyFactor = 0.0
cdreCfg.CostRoundingDecimals = -1
cdreCfg.CostShiftDigits = 0
cdreCfg.MaskDestId = ""
cdreCfg.MaskLength = 0
cdreCfg.ExportDir = "/var/log/cgrates/cdre"
if flds, err := NewCdreCdrFieldsFromIds(false, utils.CGRID, utils.MEDI_RUNID, utils.TOR, utils.ACCID, utils.REQTYPE, utils.DIRECTION, utils.TENANT,
utils.CATEGORY, utils.ACCOUNT, utils.SUBJECT, utils.DESTINATION, utils.SETUP_TIME, utils.ANSWER_TIME, utils.USAGE, utils.COST); err != nil {
return err
} else {
cdreCfg.ContentFields = flds
}
return nil
}
type CdreCdrField struct {
Name string
Type string
Value string
Width int
Strip string
Padding string
Layout string
Filter *utils.RSRField
Mandatory bool
valueAsRsrField *utils.RSRField // Cached if the need arrises
}
func (cdrField *CdreCdrField) ValueAsRSRField() *utils.RSRField {
return cdrField.valueAsRsrField
}
// Should be called on .fwv configuration without providing default values for fixed with parameters
func (cdrField *CdreCdrField) setDefaultFieldProperties(fixedWidth bool) error {
if cdrField.valueAsRsrField == nil {
return errors.New("Missing valueAsRsrField")
}
switch cdrField.valueAsRsrField.Id {
case utils.CGRID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 40
}
case utils.ORDERID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 11
cdrField.Padding = "left"
}
case utils.TOR:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 6
cdrField.Padding = "left"
}
case utils.ACCID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 36
cdrField.Strip = "left"
cdrField.Padding = "left"
}
case utils.CDRHOST:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 15
cdrField.Strip = "left"
cdrField.Padding = "left"
}
case utils.CDRSOURCE:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 15
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.REQTYPE:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 13
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.DIRECTION:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 4
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.TENANT:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.CATEGORY:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 10
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.ACCOUNT:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.SUBJECT:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.DESTINATION:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.SETUP_TIME:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 30
cdrField.Strip = "xright"
cdrField.Padding = "left"
cdrField.Layout = "2006-01-02T15:04:05Z07:00"
}
case utils.ANSWER_TIME:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 30
cdrField.Strip = "xright"
cdrField.Padding = "left"
cdrField.Layout = "2006-01-02T15:04:05Z07:00"
}
case utils.USAGE:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 30
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.MEDI_RUNID:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 20
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
case utils.COST:
cdrField.Mandatory = true
if fixedWidth {
cdrField.Width = 24
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
default:
cdrField.Mandatory = false
if fixedWidth {
cdrField.Width = 30
cdrField.Strip = "xright"
cdrField.Padding = "left"
}
}
return nil
}

483
config/cdreconfig_test.go Normal file
View File

@@ -0,0 +1,483 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"github.com/cgrates/cgrates/utils"
"reflect"
"testing"
)
func TestCdreCfgNewCdreCdrFieldsFromIds(t *testing.T) {
expectedFlds := []*CdreCdrField{
&CdreCdrField{
Name: utils.CGRID,
Type: utils.CDRFIELD,
Value: utils.CGRID,
Width: 40,
Strip: "",
Padding: "",
Layout: "",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
},
&CdreCdrField{
Name: "extra1",
Type: utils.CDRFIELD,
Value: "extra1",
Width: 30,
Strip: "xright",
Padding: "left",
Layout: "",
Mandatory: false,
valueAsRsrField: &utils.RSRField{Id: "extra1"},
},
}
if cdreFlds, err := NewCdreCdrFieldsFromIds(true, utils.CGRID, "extra1"); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(expectedFlds, cdreFlds) {
t.Errorf("Expected: %v, received: %v", expectedFlds, cdreFlds)
}
}
func TestCdreCfgValueAsRSRField(t *testing.T) {
cdreCdrFld := &CdreCdrField{
Name: utils.CGRID,
Type: utils.CDRFIELD,
Value: utils.CGRID,
Width: 10,
Strip: "xright",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
}
if rsrVal := cdreCdrFld.ValueAsRSRField(); rsrVal != cdreCdrFld.valueAsRsrField {
t.Error("Unexpected value received: ", rsrVal)
}
}
func TestCdreCfgNewDefaultCdreConfig(t *testing.T) {
eCdreCfg := new(CdreConfig)
eCdreCfg.CdrFormat = utils.CSV
eCdreCfg.FieldSeparator = utils.CSV_SEP
eCdreCfg.DataUsageMultiplyFactor = 0.0
eCdreCfg.CostMultiplyFactor = 0.0
eCdreCfg.CostRoundingDecimals = -1
eCdreCfg.CostShiftDigits = 0
eCdreCfg.MaskDestId = ""
eCdreCfg.MaskLength = 0
eCdreCfg.ExportDir = "/var/log/cgrates/cdre"
eCdreCfg.ContentFields = []*CdreCdrField{
&CdreCdrField{
Name: utils.CGRID,
Type: utils.CDRFIELD,
Value: utils.CGRID,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
},
&CdreCdrField{
Name: utils.MEDI_RUNID,
Type: utils.CDRFIELD,
Value: utils.MEDI_RUNID,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
},
&CdreCdrField{
Name: utils.TOR,
Type: utils.CDRFIELD,
Value: utils.TOR,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
},
&CdreCdrField{
Name: utils.ACCID,
Type: utils.CDRFIELD,
Value: utils.ACCID,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
},
&CdreCdrField{
Name: utils.REQTYPE,
Type: utils.CDRFIELD,
Value: utils.REQTYPE,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
},
&CdreCdrField{
Name: utils.DIRECTION,
Type: utils.CDRFIELD,
Value: utils.DIRECTION,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
},
&CdreCdrField{
Name: utils.TENANT,
Type: utils.CDRFIELD,
Value: utils.TENANT,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
},
&CdreCdrField{
Name: utils.CATEGORY,
Type: utils.CDRFIELD,
Value: utils.CATEGORY,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
},
&CdreCdrField{
Name: utils.ACCOUNT,
Type: utils.CDRFIELD,
Value: utils.ACCOUNT,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
},
&CdreCdrField{
Name: utils.SUBJECT,
Type: utils.CDRFIELD,
Value: utils.SUBJECT,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
},
&CdreCdrField{
Name: utils.DESTINATION,
Type: utils.CDRFIELD,
Value: utils.DESTINATION,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
},
&CdreCdrField{
Name: utils.SETUP_TIME,
Type: utils.CDRFIELD,
Value: utils.SETUP_TIME,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
},
&CdreCdrField{
Name: utils.ANSWER_TIME,
Type: utils.CDRFIELD,
Value: utils.ANSWER_TIME,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
},
&CdreCdrField{
Name: utils.USAGE,
Type: utils.CDRFIELD,
Value: utils.USAGE,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
},
&CdreCdrField{
Name: utils.COST,
Type: utils.CDRFIELD,
Value: utils.COST,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.COST},
},
}
if cdreCfg, err := NewDefaultCdreConfig(); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCfg, cdreCfg) {
t.Errorf("Expecting: %v, received: %v", eCdreCfg, cdreCfg)
}
}
func TestCdreCfgSetDefaultFieldProperties(t *testing.T) {
cdreCdrFld := &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
}
eCdreCdrFld := &CdreCdrField{
Width: 40,
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ORDERID},
}
eCdreCdrFld = &CdreCdrField{
Width: 11,
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ORDERID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
}
eCdreCdrFld = &CdreCdrField{
Width: 6,
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
}
eCdreCdrFld = &CdreCdrField{
Width: 36,
Strip: "left",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST},
}
eCdreCdrFld = &CdreCdrField{
Width: 15,
Strip: "left",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE},
}
eCdreCdrFld = &CdreCdrField{
Width: 15,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
}
eCdreCdrFld = &CdreCdrField{
Width: 13,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
}
eCdreCdrFld = &CdreCdrField{
Width: 4,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
}
eCdreCdrFld = &CdreCdrField{
Width: 10,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Layout: "2006-01-02T15:04:05Z07:00",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Layout: "2006-01-02T15:04:05Z07:00",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
}
eCdreCdrFld = &CdreCdrField{
Width: 20,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: utils.COST},
}
eCdreCdrFld = &CdreCdrField{
Width: 24,
Strip: "xright",
Padding: "left",
Mandatory: true,
valueAsRsrField: &utils.RSRField{Id: utils.COST},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
cdreCdrFld = &CdreCdrField{
valueAsRsrField: &utils.RSRField{Id: "extra_1"},
}
eCdreCdrFld = &CdreCdrField{
Width: 30,
Strip: "xright",
Padding: "left",
Mandatory: false,
valueAsRsrField: &utils.RSRField{Id: "extra_1"},
}
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
}
}

207
config/cdrstatsconfig.go Normal file
View File

@@ -0,0 +1,207 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"code.google.com/p/goconf/conf"
"github.com/cgrates/cgrates/utils"
"strconv"
"time"
)
// Parse the configuration file for CDRStatConfigs
func ParseCfgDefaultCDRStatsConfig(c *conf.ConfigFile) (*CdrStatsConfig, error) {
var err error
csCfg := NewCdrStatsConfigWithDefaults()
if hasOpt := c.HasOption("cdrstats", "queue_length"); hasOpt {
csCfg.QueueLength, _ = c.GetInt("cdrstats", "queue_length")
}
if hasOpt := c.HasOption("cdrstats", "time_window"); hasOpt {
durStr, _ := c.GetString("cdrstats", "time_window")
if csCfg.TimeWindow, err = utils.ParseDurationWithSecs(durStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "metrics"); hasOpt {
metricsStr, _ := c.GetString("cdrstats", "metrics")
if csCfg.Metrics, err = ConfigSlice(metricsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "setup_interval"); hasOpt {
setupIntervalStr, _ := c.GetString("cdrstats", "setup_interval")
if len(setupIntervalStr) != 0 { // If we parse empty, will get empty time, we prefer nil
if setupIntervalSlc, err := ConfigSlice(setupIntervalStr); err != nil {
return nil, err
} else {
for _, setupTimeStr := range setupIntervalSlc {
if setupTime, err := utils.ParseTimeDetectLayout(setupTimeStr); err != nil {
return nil, err
} else {
csCfg.SetupInterval = append(csCfg.SetupInterval, setupTime)
}
}
}
}
}
if hasOpt := c.HasOption("cdrstats", "tors"); hasOpt {
torsStr, _ := c.GetString("cdrstats", "tors")
if csCfg.TORs, err = ConfigSlice(torsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "cdr_hosts"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "cdr_hosts")
if csCfg.CdrHosts, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "cdr_sources"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "cdr_sources")
if csCfg.CdrSources, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "req_types"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "req_types")
if csCfg.ReqTypes, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "directions"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "directions")
if csCfg.Directions, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "tenants"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "tenants")
if csCfg.Tenants, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "categories"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "categories")
if csCfg.Categories, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "accounts"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "accounts")
if csCfg.Accounts, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "subjects"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "subjects")
if csCfg.Subjects, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "destination_prefixes"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "destination_prefixes")
if csCfg.DestinationPrefixes, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "usage_interval"); hasOpt {
usageIntervalStr, _ := c.GetString("cdrstats", "usage_interval")
if usageIntervalSlc, err := ConfigSlice(usageIntervalStr); err != nil {
return nil, err
} else {
for _, usageDurStr := range usageIntervalSlc {
if usageDur, err := utils.ParseDurationWithSecs(usageDurStr); err != nil {
return nil, err
} else {
csCfg.UsageInterval = append(csCfg.UsageInterval, usageDur)
}
}
}
}
if hasOpt := c.HasOption("cdrstats", "mediation_run_ids"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "mediation_run_ids")
if csCfg.MediationRunIds, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "rated_accounts"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "rated_accounts")
if csCfg.RatedAccounts, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "rated_subjects"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "rated_subjects")
if csCfg.RatedSubjects, err = ConfigSlice(valsStr); err != nil {
return nil, err
}
}
if hasOpt := c.HasOption("cdrstats", "cost_intervals"); hasOpt {
valsStr, _ := c.GetString("cdrstats", "cost_intervals")
if costSlc, err := ConfigSlice(valsStr); err != nil {
return nil, err
} else {
for _, costStr := range costSlc {
if cost, err := strconv.ParseFloat(costStr, 64); err != nil {
return nil, err
} else {
csCfg.CostInterval = append(csCfg.CostInterval, cost)
}
}
}
}
return csCfg, nil
}
func NewCdrStatsConfigWithDefaults() *CdrStatsConfig {
csCfg := new(CdrStatsConfig)
csCfg.setDefaults()
return csCfg
}
type CdrStatsConfig struct {
Id string // Config id, unique per config instance
QueueLength int // Number of items in the stats buffer
TimeWindow time.Duration // Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
Metrics []string // ASR, ACD, ACC
SetupInterval []time.Time // 2 or less items (>= start interval,< stop_interval)
TORs []string
CdrHosts []string
CdrSources []string
ReqTypes []string
Directions []string
Tenants []string
Categories []string
Accounts []string
Subjects []string
DestinationPrefixes []string
UsageInterval []time.Duration // 2 or less items (>= Usage, <Usage)
MediationRunIds []string
RatedAccounts []string
RatedSubjects []string
CostInterval []float64 // 2 or less items, (>=Cost, <Cost)
}
func (csCfg *CdrStatsConfig) setDefaults() {
csCfg.Id = utils.META_DEFAULT
csCfg.QueueLength = 50
csCfg.TimeWindow = time.Duration(1) * time.Hour
csCfg.Metrics = []string{"ASR", "ACD", "ACC"}
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
@@ -54,109 +55,93 @@ func SetCgrConfig(cfg *CGRConfig) {
// Holds system configuration, defaults are overwritten with values from config file if found
type CGRConfig struct {
RatingDBType string
RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
RatingDBPort string // The port to bind to.
RatingDBName string // The name of the database to connect to.
RatingDBUser string // The user to sign in as.
RatingDBPass string // The user's password.
AccountDBType string
AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
AccountDBPort string // The port to bind to.
AccountDBName string // The name of the database to connect to.
AccountDBUser string // The user to sign in as.
AccountDBPass string // The user's password.
StorDBType string // Should reflect the database type used to store logs
StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
StorDBPort string // Th e port to bind to.
StorDBName string // The name of the database to connect to.
StorDBUser string // The user to sign in as.
StorDBPass string // The user's password.
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
RPCJSONListen string // RPC JSON listening address
RPCGOBListen string // RPC GOB listening address
HTTPListen string // HTTP listening address
DefaultReqType string // Use this request type if not defined on top
DefaultTOR string // set default type of record
DefaultTenant string // set default tenant
DefaultSubject string // set default rating subject, useful in case of fallback
RoundingMethod string // Rounding method for the end price: <*up|*middle|*down>
RoundingDecimals int // Number of decimals to round end prices at
XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
RaterEnabled bool // start standalone server (no balancer)
RaterBalancer string // balancer address host:port
BalancerEnabled bool
SchedulerEnabled bool
CDRSEnabled bool // Enable CDR Server service
CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
CdreCdrFormat string // Format of the exported CDRs. <csv>
CdreDir string // Path towards exported cdrs directory
CdreExportedFields []*utils.RSRField // List of fields in the exported CDRs
CdreFWXmlTemplate *CgrXmlCdreFwCfg // Use this configuration as export template in case of fixed fields length
CdrcEnabled bool // Enable CDR client functionality
CdrcCdrs string // Address where to reach CDR server
CdrcCdrsMethod string // Mechanism to use when posting CDRs on server <http_cgr>
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
CdrcCdrType string // CDR file format <csv>.
CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
CdrcAccIdField string // Accounting id field identifier. Use index number in case of .csv cdrs.
CdrcReqTypeField string // Request type field identifier. Use index number in case of .csv cdrs.
CdrcDirectionField string // Direction field identifier. Use index numbers in case of .csv cdrs.
CdrcTenantField string // Tenant field identifier. Use index numbers in case of .csv cdrs.
CdrcTorField string // Type of Record field identifier. Use index numbers in case of .csv cdrs.
CdrcAccountField string // Account field identifier. Use index numbers in case of .csv cdrs.
CdrcSubjectField string // Subject field identifier. Use index numbers in case of .csv CDRs.
CdrcDestinationField string // Destination field identifier. Use index numbers in case of .csv cdrs.
CdrcSetupTimeField string // Setup time field identifier. Use index numbers in case of .csv cdrs.
CdrcAnswerTimeField string // Answer time field identifier. Use index numbers in case of .csv cdrs.
CdrcDurationField string // Duration field identifier. Use index numbers in case of .csv cdrs.
CdrcExtraFields []string // Extra fields to extract, special format in case of .csv "field1:index1,field2:index2"
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMRaterReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMMaxCallDuration time.Duration // The maximum duration of a call
SMRunIds []string // Identifiers of additional sessions control.
SMReqTypeFields []string // Name of request type fields to be used during additional sessions control <""|*default|field_name>.
SMDirectionFields []string // Name of direction fields to be used during additional sessions control <""|*default|field_name>.
SMTenantFields []string // Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
SMTORFields []string // Name of tor fields to be used during additional sessions control <""|*default|field_name>.
SMAccountFields []string // Name of account fields to be used during additional sessions control <""|*default|field_name>.
SMSubjectFields []string // Name of fields to be used during additional sessions control <""|*default|field_name>.
SMDestFields []string // Name of destination fields to be used during additional sessions control <""|*default|field_name>.
SMSetupTimeFields []string // Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
SMAnswerTimeFields []string // Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
SMDurationFields []string // Name of duration fields to be used during additional sessions control <""|*default|field_name>.
MediatorEnabled bool // Starts Mediator service: <true|false>.
MediatorRater string // Address where to reach the Rater: <internal|x.y.z.y:1234>
MediatorRaterReconnects int // Number of reconnects to rater before giving up.
MediatorRunIds []string // Identifiers for each mediation run on CDRs
MediatorReqTypeFields []string // Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
MediatorDirectionFields []string // Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorTenantFields []string // Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorTORFields []string // Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorAccountFields []string // Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorSubjectFields []string // Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorDestFields []string // Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorSetupTimeFields []string // Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorAnswerTimeFields []string // Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
MediatorDurationFields []string // Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
HistoryAgentEnabled bool // Starts History as an agent: <true|false>.
HistoryServer string // Address where to reach the master history server: <internal|x.y.z.y:1234>
HistoryServerEnabled bool // Starts History as server: <true|false>.
HistoryDir string // Location on disk where to store history files.
HistorySaveInterval time.Duration // The timout duration between history writes
MailerServer string // The server to use when sending emails out
MailerAuthUser string // Authenticate to email server using this user
MailerAuthPass string // Authenticate to email server with this password
MailerFromAddr string // From address used when sending emails out
RatingDBType string
RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
RatingDBPort string // The port to bind to.
RatingDBName string // The name of the database to connect to.
RatingDBUser string // The user to sign in as.
RatingDBPass string // The user's password.
AccountDBType string
AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
AccountDBPort string // The port to bind to.
AccountDBName string // The name of the database to connect to.
AccountDBUser string // The user to sign in as.
AccountDBPass string // The user's password.
StorDBType string // Should reflect the database type used to store logs
StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
StorDBPort string // Th e port to bind to.
StorDBName string // The name of the database to connect to.
StorDBUser string // The user to sign in as.
StorDBPass string // The user's password.
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
RPCJSONListen string // RPC JSON listening address
RPCGOBListen string // RPC GOB listening address
HTTPListen string // HTTP listening address
DefaultReqType string // Use this request type if not defined on top
DefaultCategory string // set default type of record
DefaultTenant string // set default tenant
DefaultSubject string // set default rating subject, useful in case of fallback
RoundingDecimals int // Number of decimals to round end prices at
HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate
XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
RaterEnabled bool // start standalone server (no balancer)
RaterBalancer string // balancer address host:port
BalancerEnabled bool
SchedulerEnabled bool
CDRSEnabled bool // Enable CDR Server service
CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
CDRSStats string // Address where to reach the Mediator. <""|intenal>
CDRSStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
CDRStatsEnabled bool // Enable CDR Stats service
CDRStatConfig *CdrStatsConfig // Active cdr stats configuration instances
CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API
CdrcEnabled bool // Enable CDR client functionality
CdrcCdrs string // Address where to reach CDR server
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
CdrcCdrType string // CDR file format <csv>.
CdrcCsvSep string // Separator used in case of csv files. One character only supported.
CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
CdrcCdrFields map[string][]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs.
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMCdrS string //
SMReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMMaxCallDuration time.Duration // The maximum duration of a call
SMMinCallDuration time.Duration // Only authorize calls with allowed duration bigger than this
MediatorEnabled bool // Starts Mediator service: <true|false>.
MediatorReconnects int // Number of reconnects to rater before giving up.
MediatorRater string
MediatorStats string // Address where to reach the Rater: <internal|x.y.z.y:1234>
MediatorStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
DerivedChargers utils.DerivedChargers // System wide derived chargers, added to the account level ones
CombinedDerivedChargers bool // Combine accounts specific derived_chargers with server configured
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
FSMinDurLowBalance time.Duration // Threshold which will trigger low balance warnings
FSLowBalanceAnnFile string // File to be played when low balance is reached
FSEmptyBalanceContext string // If defined, call will be transfered to this context on empty balance
FSEmptyBalanceAnnFile string // File to be played before disconnecting prepaid calls (applies only if no context defined)
FSCdrExtraFields []*utils.RSRField // Extra fields to store in CDRs in case of processing them
OsipsListenUdp string // Address where to listen for event datagrams coming from OpenSIPS
OsipsMiAddr string // Adress where to reach OpenSIPS mi_datagram module
OsipsEvSubscInterval time.Duration // Refresh event subscription at this interval
OsipsReconnects int // Number of attempts on connect failure.
HistoryAgentEnabled bool // Starts History as an agent: <true|false>.
HistoryServer string // Address where to reach the master history server: <internal|x.y.z.y:1234>
HistoryServerEnabled bool // Starts History as server: <true|false>.
HistoryDir string // Location on disk where to store history files.
HistorySaveInterval time.Duration // The timout duration between history writes
MailerServer string // The server to use when sending emails out
MailerAuthUser string // Authenticate to email server using this user
MailerAuthPass string // Authenticate to email server with this password
MailerFromAddr string // From address used when sending emails out
DataFolderPath string // Path towards data folder, for tests internal usage, not loading out of .cfg options
}
func (self *CGRConfig) setDefaults() error {
@@ -183,11 +168,11 @@ func (self *CGRConfig) setDefaults() error {
self.RPCGOBListen = "127.0.0.1:2013"
self.HTTPListen = "127.0.0.1:2080"
self.DefaultReqType = utils.RATED
self.DefaultTOR = "call"
self.DefaultCategory = "call"
self.DefaultTenant = "cgrates.org"
self.DefaultSubject = "cgrates"
self.RoundingMethod = utils.ROUNDING_MIDDLE
self.RoundingDecimals = 4
self.RoundingDecimals = 10
self.HttpSkipTlsVerify = false
self.XmlCfgDocument = nil
self.RaterEnabled = false
self.RaterBalancer = ""
@@ -196,125 +181,87 @@ func (self *CGRConfig) setDefaults() error {
self.CDRSEnabled = false
self.CDRSExtraFields = []*utils.RSRField{}
self.CDRSMediator = ""
self.CdreCdrFormat = "csv"
self.CdreDir = "/var/log/cgrates/cdr/cdrexport/csv"
self.CDRSStats = ""
self.CDRSStoreDisable = false
self.CDRStatsEnabled = false
self.CDRStatConfig = NewCdrStatsConfigWithDefaults()
self.CdreDefaultInstance, _ = NewDefaultCdreConfig()
self.CdrcEnabled = false
self.CdrcCdrs = utils.INTERNAL
self.CdrcCdrsMethod = "http_cgr"
self.CdrcRunDelay = time.Duration(0)
self.CdrcCdrType = "csv"
self.CdrcCdrInDir = "/var/log/cgrates/cdr/cdrc/in"
self.CdrcCdrOutDir = "/var/log/cgrates/cdr/cdrc/out"
self.CdrcSourceId = "freeswitch_csv"
self.CdrcAccIdField = "0"
self.CdrcReqTypeField = "1"
self.CdrcDirectionField = "2"
self.CdrcTenantField = "3"
self.CdrcTorField = "4"
self.CdrcAccountField = "5"
self.CdrcSubjectField = "6"
self.CdrcDestinationField = "7"
self.CdrcSetupTimeField = "8"
self.CdrcAnswerTimeField = "9"
self.CdrcDurationField = "10"
self.CdrcExtraFields = []string{}
self.CdrcCdrType = utils.CSV
self.CdrcCsvSep = string(utils.CSV_SEP)
self.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
self.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
self.CdrcSourceId = "csv"
self.CdrcCdrFields = map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
}
self.MediatorEnabled = false
self.MediatorRater = "internal"
self.MediatorRaterReconnects = 3
self.MediatorRunIds = []string{}
self.MediatorSubjectFields = []string{}
self.MediatorReqTypeFields = []string{}
self.MediatorDirectionFields = []string{}
self.MediatorTenantFields = []string{}
self.MediatorTORFields = []string{}
self.MediatorAccountFields = []string{}
self.MediatorDestFields = []string{}
self.MediatorSetupTimeFields = []string{}
self.MediatorAnswerTimeFields = []string{}
self.MediatorDurationFields = []string{}
self.MediatorRater = utils.INTERNAL
self.MediatorReconnects = 3
self.MediatorStats = utils.INTERNAL
self.MediatorStoreDisable = false
self.DerivedChargers = make(utils.DerivedChargers, 0)
self.CombinedDerivedChargers = true
self.SMEnabled = false
self.SMSwitchType = FS
self.SMRater = "internal"
self.SMRaterReconnects = 3
self.SMRater = utils.INTERNAL
self.SMCdrS = ""
self.SMReconnects = 3
self.SMDebitInterval = 10
self.SMMaxCallDuration = time.Duration(3) * time.Hour
self.SMRunIds = []string{}
self.SMReqTypeFields = []string{}
self.SMDirectionFields = []string{}
self.SMTenantFields = []string{}
self.SMTORFields = []string{}
self.SMAccountFields = []string{}
self.SMSubjectFields = []string{}
self.SMDestFields = []string{}
self.SMSetupTimeFields = []string{}
self.SMAnswerTimeFields = []string{}
self.SMDurationFields = []string{}
self.SMMinCallDuration = time.Duration(0)
self.FreeswitchServer = "127.0.0.1:8021"
self.FreeswitchPass = "ClueCon"
self.FreeswitchReconnects = 5
self.FSMinDurLowBalance = time.Duration(5) * time.Second
self.FSLowBalanceAnnFile = ""
self.FSEmptyBalanceContext = ""
self.FSEmptyBalanceAnnFile = ""
self.FSCdrExtraFields = []*utils.RSRField{}
self.OsipsListenUdp = "127.0.0.1:2020"
self.OsipsMiAddr = "127.0.0.1:8020"
self.OsipsEvSubscInterval = time.Duration(60) * time.Second
self.OsipsReconnects = 3
self.HistoryAgentEnabled = false
self.HistoryServerEnabled = false
self.HistoryServer = "internal"
self.HistoryServer = utils.INTERNAL
self.HistoryDir = "/var/log/cgrates/history"
self.HistorySaveInterval = time.Duration(1) * time.Second
self.MailerServer = "localhost:25"
self.MailerAuthUser = "cgrates"
self.MailerAuthPass = "CGRateS.org"
self.MailerFromAddr = "cgr-mailer@localhost.localdomain"
self.CdreExportedFields = []*utils.RSRField{
&utils.RSRField{Id: utils.CGRID},
&utils.RSRField{Id: utils.MEDI_RUNID},
&utils.RSRField{Id: utils.ACCID},
&utils.RSRField{Id: utils.CDRHOST},
&utils.RSRField{Id: utils.REQTYPE},
&utils.RSRField{Id: utils.DIRECTION},
&utils.RSRField{Id: utils.TENANT},
&utils.RSRField{Id: utils.TOR},
&utils.RSRField{Id: utils.ACCOUNT},
&utils.RSRField{Id: utils.SUBJECT},
&utils.RSRField{Id: utils.DESTINATION},
&utils.RSRField{Id: utils.SETUP_TIME},
&utils.RSRField{Id: utils.ANSWER_TIME},
&utils.RSRField{Id: utils.DURATION},
&utils.RSRField{Id: utils.COST},
}
self.DataFolderPath = "/usr/share/cgrates/"
return nil
}
func (self *CGRConfig) checkConfigSanity() error {
// Cdre sanity check for fixed_width
if self.CdreCdrFormat == utils.CDRE_FIXED_WIDTH {
if self.XmlCfgDocument == nil {
return errors.New("Need XmlConfigurationDocument for fixed_width cdr export")
} else if self.CdreFWXmlTemplate == nil {
return errors.New("Need XmlTemplate for fixed_width cdr export")
if self.CdrcEnabled {
if len(self.CdrcCdrFields) == 0 {
return errors.New("CdrC enabled but no fields to be processed defined!")
}
if self.CdrcCdrType == utils.CSV {
for _, rsrFldLst := range self.CdrcCdrFields {
for _, rsrFld := range rsrFldLst {
if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
}
}
}
}
}
// SessionManager should have same fields config length for session emulation
if len(self.SMReqTypeFields) != len(self.SMRunIds) ||
len(self.SMDirectionFields) != len(self.SMRunIds) ||
len(self.SMTenantFields) != len(self.SMRunIds) ||
len(self.SMTORFields) != len(self.SMRunIds) ||
len(self.SMAccountFields) != len(self.SMRunIds) ||
len(self.SMSubjectFields) != len(self.SMRunIds) ||
len(self.SMDestFields) != len(self.SMRunIds) ||
len(self.SMSetupTimeFields) != len(self.SMRunIds) ||
len(self.SMAnswerTimeFields) != len(self.SMRunIds) ||
len(self.SMDurationFields) != len(self.SMRunIds) {
return errors.New("<ConfigSanity> Inconsistent fields length for SessionManager session emulation")
}
// Mediator needs to have consistent extra fields definition
if len(self.MediatorReqTypeFields) != len(self.MediatorRunIds) ||
len(self.MediatorDirectionFields) != len(self.MediatorRunIds) ||
len(self.MediatorTenantFields) != len(self.MediatorRunIds) ||
len(self.MediatorTORFields) != len(self.MediatorRunIds) ||
len(self.MediatorAccountFields) != len(self.MediatorRunIds) ||
len(self.MediatorSubjectFields) != len(self.MediatorRunIds) ||
len(self.MediatorDestFields) != len(self.MediatorRunIds) ||
len(self.MediatorSetupTimeFields) != len(self.MediatorRunIds) ||
len(self.MediatorAnswerTimeFields) != len(self.MediatorRunIds) ||
len(self.MediatorDurationFields) != len(self.MediatorRunIds) {
return errors.New("<ConfigSanity> Inconsistent fields length for Mediator extra fields")
}
return nil
}
@@ -328,12 +275,8 @@ func NewDefaultCGRConfig() (*CGRConfig, error) {
return cfg, nil
}
// Instantiate a new CGRConfig setting defaults or reading from file
func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
c, err := conf.ReadConfigFile(*cfgPath)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
// Unifies the config handling for both tests and real path
func NewCGRConfig(c *conf.ConfigFile) (*CGRConfig, error) {
cfg, err := loadConfig(c)
if err != nil {
return nil, err
@@ -344,26 +287,28 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
return cfg, nil
}
func NewCGRConfigBytes(data []byte) (*CGRConfig, error) {
// Instantiate a new CGRConfig setting defaults or reading from file
func NewCGRConfigFromFile(cfgPath *string) (*CGRConfig, error) {
c, err := conf.ReadConfigFile(*cfgPath)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
return NewCGRConfig(c)
}
func NewCGRConfigFromBytes(data []byte) (*CGRConfig, error) {
c, err := conf.ReadConfigBytes(data)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
cfg, err := loadConfig(c)
if err != nil {
return nil, err
}
if err := cfg.checkConfigSanity(); err != nil {
return nil, err
}
return cfg, nil
return NewCGRConfig(c)
}
func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
cfg := &CGRConfig{}
cfg.setDefaults()
var hasOpt bool
var errParse error
var err error
if hasOpt = c.HasOption("global", "ratingdb_type"); hasOpt {
cfg.RatingDBType, _ = c.GetString("global", "ratingdb_type")
}
@@ -433,8 +378,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("global", "default_reqtype"); hasOpt {
cfg.DefaultReqType, _ = c.GetString("global", "default_reqtype")
}
if hasOpt = c.HasOption("global", "default_tor"); hasOpt {
cfg.DefaultTOR, _ = c.GetString("global", "default_tor")
if hasOpt = c.HasOption("global", "default_category"); hasOpt {
cfg.DefaultCategory, _ = c.GetString("global", "default_category")
}
if hasOpt = c.HasOption("global", "default_tenant"); hasOpt {
cfg.DefaultTenant, _ = c.GetString("global", "default_tenant")
@@ -442,12 +387,12 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("global", "default_subject"); hasOpt {
cfg.DefaultSubject, _ = c.GetString("global", "default_subject")
}
if hasOpt = c.HasOption("global", "rounding_method"); hasOpt {
cfg.RoundingMethod, _ = c.GetString("global", "rounding_method")
}
if hasOpt = c.HasOption("global", "rounding_decimals"); hasOpt {
cfg.RoundingDecimals, _ = c.GetInt("global", "rounding_decimals")
}
if hasOpt = c.HasOption("global", "http_skip_tls_veify"); hasOpt {
cfg.HttpSkipTlsVerify, _ = c.GetBool("global", "http_skip_tls_veify")
}
// XML config path defined, try loading the document
if hasOpt = c.HasOption("global", "xmlcfg_path"); hasOpt {
xmlCfgPath, _ := c.GetString("global", "xmlcfg_path")
@@ -478,8 +423,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
}
if hasOpt = c.HasOption("cdrs", "extra_fields"); hasOpt {
extraFieldsStr, _ := c.GetString("cdrs", "extra_fields")
if extraFields, err := ParseRSRFields(extraFieldsStr); err != nil {
return nil, errParse
if extraFields, err := utils.ParseRSRFields(extraFieldsStr, utils.FIELDS_SEP); err != nil {
return nil, err
} else {
cfg.CDRSExtraFields = extraFields
}
@@ -487,27 +432,56 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("cdrs", "mediator"); hasOpt {
cfg.CDRSMediator, _ = c.GetString("cdrs", "mediator")
}
if hasOpt = c.HasOption("cdrs", "cdrstats"); hasOpt {
cfg.CDRSStats, _ = c.GetString("cdrs", "cdrstats")
}
if hasOpt = c.HasOption("cdrs", "store_disable"); hasOpt {
cfg.CDRSStoreDisable, _ = c.GetBool("cdrs", "store_disable")
}
if hasOpt = c.HasOption("cdrstats", "enabled"); hasOpt {
cfg.CDRStatsEnabled, _ = c.GetBool("cdrstats", "enabled")
}
if cfg.CDRStatConfig, err = ParseCfgDefaultCDRStatsConfig(c); err != nil {
return nil, err
}
if hasOpt = c.HasOption("cdre", "cdr_format"); hasOpt {
cfg.CdreCdrFormat, _ = c.GetString("cdre", "cdr_format")
cfg.CdreDefaultInstance.CdrFormat, _ = c.GetString("cdre", "cdr_format")
}
if hasOpt = c.HasOption("cdre", "mask_destination_id"); hasOpt {
cfg.CdreDefaultInstance.MaskDestId, _ = c.GetString("cdre", "mask_destination_id")
}
if hasOpt = c.HasOption("cdre", "mask_length"); hasOpt {
cfg.CdreDefaultInstance.MaskLength, _ = c.GetInt("cdre", "mask_length")
}
if hasOpt = c.HasOption("cdre", "data_usage_multiply_factor"); hasOpt {
cfg.CdreDefaultInstance.DataUsageMultiplyFactor, _ = c.GetFloat64("cdre", "data_usage_multiply_factor")
}
if hasOpt = c.HasOption("cdre", "cost_multiply_factor"); hasOpt {
cfg.CdreDefaultInstance.CostMultiplyFactor, _ = c.GetFloat64("cdre", "cost_multiply_factor")
}
if hasOpt = c.HasOption("cdre", "cost_rounding_decimals"); hasOpt {
cfg.CdreDefaultInstance.CostRoundingDecimals, _ = c.GetInt("cdre", "cost_rounding_decimals")
}
if hasOpt = c.HasOption("cdre", "cost_shift_digits"); hasOpt {
cfg.CdreDefaultInstance.CostShiftDigits, _ = c.GetInt("cdre", "cost_shift_digits")
}
if hasOpt = c.HasOption("cdre", "export_template"); hasOpt { // Load configs for csv normally from template, fixed_width from xml file
exportTemplate, _ := c.GetString("cdre", "export_template")
if cfg.CdreCdrFormat != utils.CDRE_FIXED_WIDTH { // Csv most likely
if extraFields, err := ParseRSRFields(exportTemplate); err != nil {
return nil, errParse
} else {
cfg.CdreExportedFields = extraFields
if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
if xmlTemplates := cfg.XmlCfgDocument.GetCdreCfgs(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); xmlTemplates != nil {
cfg.CdreDefaultInstance = xmlTemplates[exportTemplate[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig()
}
} else if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
if xmlTemplate, err := cfg.XmlCfgDocument.GetCdreFWCfg(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil {
} else { // Not loading out of template
if flds, err := NewCdreCdrFieldsFromIds(cfg.CdreDefaultInstance.CdrFormat == utils.CDRE_FIXED_WIDTH,
strings.Split(exportTemplate, string(utils.CSV_SEP))...); err != nil {
return nil, err
} else {
cfg.CdreFWXmlTemplate = xmlTemplate
cfg.CdreDefaultInstance.ContentFields = flds
}
}
}
if hasOpt = c.HasOption("cdre", "export_dir"); hasOpt {
cfg.CdreDir, _ = c.GetString("cdre", "export_dir")
cfg.CdreDefaultInstance.ExportDir, _ = c.GetString("cdre", "export_dir")
}
if hasOpt = c.HasOption("cdrc", "enabled"); hasOpt {
cfg.CdrcEnabled, _ = c.GetBool("cdrc", "enabled")
@@ -515,18 +489,18 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("cdrc", "cdrs"); hasOpt {
cfg.CdrcCdrs, _ = c.GetString("cdrc", "cdrs")
}
if hasOpt = c.HasOption("cdrc", "cdrs_method"); hasOpt {
cfg.CdrcCdrsMethod, _ = c.GetString("cdrc", "cdrs_method")
}
if hasOpt = c.HasOption("cdrc", "run_delay"); hasOpt {
durStr, _ := c.GetString("cdrc", "run_delay")
if cfg.CdrcRunDelay, errParse = utils.ParseDurationWithSecs(durStr); errParse != nil {
return nil, errParse
if cfg.CdrcRunDelay, err = utils.ParseDurationWithSecs(durStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("cdrc", "cdr_type"); hasOpt {
cfg.CdrcCdrType, _ = c.GetString("cdrc", "cdr_type")
}
if hasOpt = c.HasOption("cdrc", "csv_separator"); hasOpt {
cfg.CdrcCsvSep, _ = c.GetString("cdrc", "csv_separator")
}
if hasOpt = c.HasOption("cdrc", "cdr_in_dir"); hasOpt {
cfg.CdrcCdrInDir, _ = c.GetString("cdrc", "cdr_in_dir")
}
@@ -536,42 +510,26 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("cdrc", "cdr_source_id"); hasOpt {
cfg.CdrcSourceId, _ = c.GetString("cdrc", "cdr_source_id")
}
if hasOpt = c.HasOption("cdrc", "accid_field"); hasOpt {
cfg.CdrcAccIdField, _ = c.GetString("cdrc", "accid_field")
}
if hasOpt = c.HasOption("cdrc", "reqtype_field"); hasOpt {
cfg.CdrcReqTypeField, _ = c.GetString("cdrc", "reqtype_field")
}
if hasOpt = c.HasOption("cdrc", "direction_field"); hasOpt {
cfg.CdrcDirectionField, _ = c.GetString("cdrc", "direction_field")
}
if hasOpt = c.HasOption("cdrc", "tenant_field"); hasOpt {
cfg.CdrcTenantField, _ = c.GetString("cdrc", "tenant_field")
}
if hasOpt = c.HasOption("cdrc", "tor_field"); hasOpt {
cfg.CdrcTorField, _ = c.GetString("cdrc", "tor_field")
}
if hasOpt = c.HasOption("cdrc", "account_field"); hasOpt {
cfg.CdrcAccountField, _ = c.GetString("cdrc", "account_field")
}
if hasOpt = c.HasOption("cdrc", "subject_field"); hasOpt {
cfg.CdrcSubjectField, _ = c.GetString("cdrc", "subject_field")
}
if hasOpt = c.HasOption("cdrc", "destination_field"); hasOpt {
cfg.CdrcDestinationField, _ = c.GetString("cdrc", "destination_field")
}
if hasOpt = c.HasOption("cdrc", "setup_time_field"); hasOpt {
cfg.CdrcSetupTimeField, _ = c.GetString("cdrc", "setup_time_field")
}
if hasOpt = c.HasOption("cdrc", "answer_time_field"); hasOpt {
cfg.CdrcAnswerTimeField, _ = c.GetString("cdrc", "answer_time_field")
}
if hasOpt = c.HasOption("cdrc", "duration_field"); hasOpt {
cfg.CdrcDurationField, _ = c.GetString("cdrc", "duration_field")
}
if hasOpt = c.HasOption("cdrc", "extra_fields"); hasOpt {
if cfg.CdrcExtraFields, errParse = ConfigSlice(c, "cdrc", "extra_fields"); errParse != nil {
return nil, errParse
// ParseCdrcCdrFields
torIdFld, _ := c.GetString("cdrc", "tor_field")
accIdFld, _ := c.GetString("cdrc", "accid_field")
reqtypeFld, _ := c.GetString("cdrc", "reqtype_field")
directionFld, _ := c.GetString("cdrc", "direction_field")
tenantFld, _ := c.GetString("cdrc", "tenant_field")
categoryFld, _ := c.GetString("cdrc", "category_field")
acntFld, _ := c.GetString("cdrc", "account_field")
subjectFld, _ := c.GetString("cdrc", "subject_field")
destFld, _ := c.GetString("cdrc", "destination_field")
setupTimeFld, _ := c.GetString("cdrc", "setup_time_field")
answerTimeFld, _ := c.GetString("cdrc", "answer_time_field")
durFld, _ := c.GetString("cdrc", "usage_field")
extraFlds, _ := c.GetString("cdrc", "extra_fields")
if len(torIdFld) != 0 || len(accIdFld) != 0 || len(reqtypeFld) != 0 || len(directionFld) != 0 || len(tenantFld) != 0 || len(categoryFld) != 0 || len(acntFld) != 0 ||
len(subjectFld) != 0 || len(destFld) != 0 || len(setupTimeFld) != 0 || len(answerTimeFld) != 0 || len(durFld) != 0 || len(extraFlds) != 0 {
// We overwrite the defaults only if at least one of the fields were defined
if cfg.CdrcCdrFields, err = ParseCdrcCdrFields(torIdFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
setupTimeFld, answerTimeFld, durFld, extraFlds); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("mediator", "enabled"); hasOpt {
@@ -580,63 +538,14 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("mediator", "rater"); hasOpt {
cfg.MediatorRater, _ = c.GetString("mediator", "rater")
}
if hasOpt = c.HasOption("mediator", "rater_reconnects"); hasOpt {
cfg.MediatorRaterReconnects, _ = c.GetInt("mediator", "rater_reconnects")
if hasOpt = c.HasOption("mediator", "reconnects"); hasOpt {
cfg.MediatorReconnects, _ = c.GetInt("mediator", "reconnects")
}
if hasOpt = c.HasOption("mediator", "run_ids"); hasOpt {
if cfg.MediatorRunIds, errParse = ConfigSlice(c, "mediator", "run_ids"); errParse != nil {
return nil, errParse
}
if hasOpt = c.HasOption("mediator", "cdrstats"); hasOpt {
cfg.MediatorStats, _ = c.GetString("mediator", "cdrstats")
}
if hasOpt = c.HasOption("mediator", "subject_fields"); hasOpt {
if cfg.MediatorSubjectFields, errParse = ConfigSlice(c, "mediator", "subject_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "reqtype_fields"); hasOpt {
if cfg.MediatorReqTypeFields, errParse = ConfigSlice(c, "mediator", "reqtype_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "direction_fields"); hasOpt {
if cfg.MediatorDirectionFields, errParse = ConfigSlice(c, "mediator", "direction_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "tenant_fields"); hasOpt {
if cfg.MediatorTenantFields, errParse = ConfigSlice(c, "mediator", "tenant_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "tor_fields"); hasOpt {
if cfg.MediatorTORFields, errParse = ConfigSlice(c, "mediator", "tor_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "account_fields"); hasOpt {
if cfg.MediatorAccountFields, errParse = ConfigSlice(c, "mediator", "account_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "destination_fields"); hasOpt {
if cfg.MediatorDestFields, errParse = ConfigSlice(c, "mediator", "destination_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "setup_time_fields"); hasOpt {
if cfg.MediatorSetupTimeFields, errParse = ConfigSlice(c, "mediator", "setup_time_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "answer_time_fields"); hasOpt {
if cfg.MediatorAnswerTimeFields, errParse = ConfigSlice(c, "mediator", "answer_time_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("mediator", "duration_fields"); hasOpt {
if cfg.MediatorDurationFields, errParse = ConfigSlice(c, "mediator", "duration_fields"); errParse != nil {
return nil, errParse
}
if hasOpt = c.HasOption("mediator", "store_disable"); hasOpt {
cfg.MediatorStoreDisable, _ = c.GetBool("mediator", "store_disable")
}
if hasOpt = c.HasOption("session_manager", "enabled"); hasOpt {
cfg.SMEnabled, _ = c.GetBool("session_manager", "enabled")
@@ -647,71 +556,25 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("session_manager", "rater"); hasOpt {
cfg.SMRater, _ = c.GetString("session_manager", "rater")
}
if hasOpt = c.HasOption("session_manager", "rater_reconnects"); hasOpt {
cfg.SMRaterReconnects, _ = c.GetInt("session_manager", "rater_reconnects")
if hasOpt = c.HasOption("session_manager", "cdrs"); hasOpt {
cfg.SMCdrS, _ = c.GetString("session_manager", "cdrs")
}
if hasOpt = c.HasOption("session_manager", "reconnects"); hasOpt {
cfg.SMReconnects, _ = c.GetInt("session_manager", "reconnects")
}
if hasOpt = c.HasOption("session_manager", "debit_interval"); hasOpt {
cfg.SMDebitInterval, _ = c.GetInt("session_manager", "debit_interval")
}
if hasOpt = c.HasOption("session_manager", "min_call_duration"); hasOpt {
minCallDurStr, _ := c.GetString("session_manager", "min_call_duration")
if cfg.SMMinCallDuration, err = utils.ParseDurationWithSecs(minCallDurStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("session_manager", "max_call_duration"); hasOpt {
maxCallDurStr, _ := c.GetString("session_manager", "max_call_duration")
if cfg.SMMaxCallDuration, errParse = utils.ParseDurationWithSecs(maxCallDurStr); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "run_ids"); hasOpt {
if cfg.SMRunIds, errParse = ConfigSlice(c, "session_manager", "run_ids"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "reqtype_fields"); hasOpt {
if cfg.SMReqTypeFields, errParse = ConfigSlice(c, "session_manager", "reqtype_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "direction_fields"); hasOpt {
if cfg.SMDirectionFields, errParse = ConfigSlice(c, "session_manager", "direction_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "tenant_fields"); hasOpt {
if cfg.SMTenantFields, errParse = ConfigSlice(c, "session_manager", "tenant_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "tor_fields"); hasOpt {
if cfg.SMTORFields, errParse = ConfigSlice(c, "session_manager", "tor_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "account_fields"); hasOpt {
if cfg.SMAccountFields, errParse = ConfigSlice(c, "session_manager", "account_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "subject_fields"); hasOpt {
if cfg.SMSubjectFields, errParse = ConfigSlice(c, "session_manager", "subject_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "destination_fields"); hasOpt {
if cfg.SMDestFields, errParse = ConfigSlice(c, "session_manager", "destination_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "setup_time_fields"); hasOpt {
if cfg.SMSetupTimeFields, errParse = ConfigSlice(c, "session_manager", "setup_time_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "answer_time_fields"); hasOpt {
if cfg.SMAnswerTimeFields, errParse = ConfigSlice(c, "session_manager", "answer_time_fields"); errParse != nil {
return nil, errParse
}
}
if hasOpt = c.HasOption("session_manager", "duration_fields"); hasOpt {
if cfg.SMDurationFields, errParse = ConfigSlice(c, "session_manager", "duration_fields"); errParse != nil {
return nil, errParse
if cfg.SMMaxCallDuration, err = utils.ParseDurationWithSecs(maxCallDurStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("freeswitch", "server"); hasOpt {
@@ -723,6 +586,50 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
if hasOpt = c.HasOption("freeswitch", "reconnects"); hasOpt {
cfg.FreeswitchReconnects, _ = c.GetInt("freeswitch", "reconnects")
}
if hasOpt = c.HasOption("freeswitch", "min_dur_low_balance"); hasOpt {
minDurStr, _ := c.GetString("freeswitch", "min_dur_low_balance")
if cfg.FSMinDurLowBalance, err = utils.ParseDurationWithSecs(minDurStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("freeswitch", "low_balance_ann_file"); hasOpt {
cfg.FSLowBalanceAnnFile, _ = c.GetString("freeswitch", "low_balance_ann_file")
}
if hasOpt = c.HasOption("freeswitch", "empty_balance_context"); hasOpt {
cfg.FSEmptyBalanceContext, _ = c.GetString("freeswitch", "empty_balance_context")
}
if hasOpt = c.HasOption("freeswitch", "empty_balance_ann_file"); hasOpt {
cfg.FSEmptyBalanceAnnFile, _ = c.GetString("freeswitch", "empty_balance_ann_file")
}
if hasOpt = c.HasOption("freeswitch", "cdr_extra_fields"); hasOpt {
extraFieldsStr, _ := c.GetString("freeswitch", "cdr_extra_fields")
if extraFields, err := utils.ParseRSRFields(extraFieldsStr, utils.FIELDS_SEP); err != nil {
return nil, err
} else {
cfg.FSCdrExtraFields = extraFields
}
}
if hasOpt = c.HasOption("opensips", "listen_udp"); hasOpt {
cfg.OsipsListenUdp, _ = c.GetString("opensips", "listen_udp")
}
if hasOpt = c.HasOption("opensips", "mi_addr"); hasOpt {
cfg.OsipsMiAddr, _ = c.GetString("opensips", "mi_addr")
}
if hasOpt = c.HasOption("opensips", "events_subscribe_interval"); hasOpt {
evSubscIntervalStr, _ := c.GetString("opensips", "events_subscribe_interval")
if cfg.OsipsEvSubscInterval, err = utils.ParseDurationWithSecs(evSubscIntervalStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("opensips", "reconnects"); hasOpt {
cfg.OsipsReconnects, _ = c.GetInt("opensips", "reconnects")
}
if cfg.DerivedChargers, err = ParseCfgDerivedCharging(c); err != nil {
return nil, err
}
if hasOpt = c.HasOption("derived_charging", "combined_chargers"); hasOpt {
cfg.CombinedDerivedChargers, _ = c.GetBool("derived_charging", "combined_chargers")
}
if hasOpt = c.HasOption("history_agent", "enabled"); hasOpt {
cfg.HistoryAgentEnabled, _ = c.GetBool("history_agent", "enabled")
}
@@ -737,8 +644,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
}
if hasOpt = c.HasOption("history_server", "save_interval"); hasOpt {
saveIntvlStr, _ := c.GetString("history_server", "save_interval")
if cfg.HistorySaveInterval, errParse = utils.ParseDurationWithSecs(saveIntvlStr); errParse != nil {
return nil, errParse
if cfg.HistorySaveInterval, err = utils.ParseDurationWithSecs(saveIntvlStr); err != nil {
return nil, err
}
}
if hasOpt = c.HasOption("mailer", "server"); hasOpt {

View File

@@ -32,16 +32,14 @@ func TestLoadXmlCfg(t *testing.T) {
return
}
cfgPath := path.Join(*dataDir, "conf", "samples", "config_local_test.cfg")
cfg, err := NewCGRConfig(&cfgPath)
cfg, err := NewCGRConfigFromFile(&cfgPath)
if err != nil {
t.Error(err)
}
if cfg.XmlCfgDocument == nil {
t.Error("Did not load the XML Config Document")
}
if cdreFWCfg, err := cfg.XmlCfgDocument.GetCdreFWCfg("CDREFW-A"); err != nil {
t.Error(err)
} else if cdreFWCfg == nil {
if cdreFWCfg := cfg.XmlCfgDocument.GetCdreCfgs("CDREFW-A"); cdreFWCfg == nil {
t.Error("Could not retrieve CDRExporter FixedWidth config instance")
}
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ package config
import (
"fmt"
"reflect"
"regexp"
"testing"
"time"
@@ -68,77 +69,75 @@ func TestDefaults(t *testing.T) {
eCfg.RPCGOBListen = "127.0.0.1:2013"
eCfg.HTTPListen = "127.0.0.1:2080"
eCfg.DefaultReqType = utils.RATED
eCfg.DefaultTOR = "call"
eCfg.DefaultCategory = "call"
eCfg.DefaultTenant = "cgrates.org"
eCfg.DefaultSubject = "cgrates"
eCfg.RoundingMethod = utils.ROUNDING_MIDDLE
eCfg.RoundingDecimals = 4
eCfg.RoundingDecimals = 10
eCfg.HttpSkipTlsVerify = false
eCfg.XmlCfgDocument = nil
eCfg.RaterEnabled = false
eCfg.RaterBalancer = ""
eCfg.BalancerEnabled = false
eCfg.SchedulerEnabled = false
eCfg.CdreDefaultInstance, _ = NewDefaultCdreConfig()
eCfg.CDRSEnabled = false
eCfg.CDRSExtraFields = []*utils.RSRField{}
eCfg.CDRSMediator = ""
eCfg.CdreCdrFormat = "csv"
eCfg.CdreDir = "/var/log/cgrates/cdr/cdrexport/csv"
eCfg.CDRSStats = ""
eCfg.CDRSStoreDisable = false
eCfg.CDRStatsEnabled = false
eCfg.CDRStatConfig = &CdrStatsConfig{Id: utils.META_DEFAULT, QueueLength: 50, TimeWindow: time.Duration(1) * time.Hour, Metrics: []string{"ASR", "ACD", "ACC"}}
eCfg.CdrcEnabled = false
eCfg.CdrcCdrs = utils.INTERNAL
eCfg.CdrcCdrsMethod = "http_cgr"
eCfg.CdrcRunDelay = time.Duration(0)
eCfg.CdrcCdrType = "csv"
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdr/cdrc/in"
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdr/cdrc/out"
eCfg.CdrcSourceId = "freeswitch_csv"
eCfg.CdrcAccIdField = "0"
eCfg.CdrcReqTypeField = "1"
eCfg.CdrcDirectionField = "2"
eCfg.CdrcTenantField = "3"
eCfg.CdrcTorField = "4"
eCfg.CdrcAccountField = "5"
eCfg.CdrcSubjectField = "6"
eCfg.CdrcDestinationField = "7"
eCfg.CdrcSetupTimeField = "8"
eCfg.CdrcAnswerTimeField = "9"
eCfg.CdrcDurationField = "10"
eCfg.CdrcExtraFields = []string{}
eCfg.CdrcCsvSep = string(utils.CSV_SEP)
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
eCfg.CdrcSourceId = "csv"
eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
}
eCfg.MediatorEnabled = false
eCfg.MediatorRater = "internal"
eCfg.MediatorRaterReconnects = 3
eCfg.MediatorRunIds = []string{}
eCfg.MediatorSubjectFields = []string{}
eCfg.MediatorReqTypeFields = []string{}
eCfg.MediatorDirectionFields = []string{}
eCfg.MediatorTenantFields = []string{}
eCfg.MediatorTORFields = []string{}
eCfg.MediatorAccountFields = []string{}
eCfg.MediatorDestFields = []string{}
eCfg.MediatorSetupTimeFields = []string{}
eCfg.MediatorAnswerTimeFields = []string{}
eCfg.MediatorDurationFields = []string{}
eCfg.MediatorRater = utils.INTERNAL
eCfg.MediatorReconnects = 3
eCfg.MediatorStats = utils.INTERNAL
eCfg.MediatorStoreDisable = false
eCfg.SMEnabled = false
eCfg.SMSwitchType = FS
eCfg.SMRater = "internal"
eCfg.SMRaterReconnects = 3
eCfg.SMRater = utils.INTERNAL
eCfg.SMCdrS = ""
eCfg.SMReconnects = 3
eCfg.SMDebitInterval = 10
eCfg.SMMinCallDuration = time.Duration(0)
eCfg.SMMaxCallDuration = time.Duration(3) * time.Hour
eCfg.SMRunIds = []string{}
eCfg.SMReqTypeFields = []string{}
eCfg.SMDirectionFields = []string{}
eCfg.SMTenantFields = []string{}
eCfg.SMTORFields = []string{}
eCfg.SMAccountFields = []string{}
eCfg.SMSubjectFields = []string{}
eCfg.SMDestFields = []string{}
eCfg.SMSetupTimeFields = []string{}
eCfg.SMAnswerTimeFields = []string{}
eCfg.SMDurationFields = []string{}
eCfg.FreeswitchServer = "127.0.0.1:8021"
eCfg.FreeswitchPass = "ClueCon"
eCfg.FreeswitchReconnects = 5
eCfg.FSMinDurLowBalance = time.Duration(5) * time.Second
eCfg.FSLowBalanceAnnFile = ""
eCfg.FSEmptyBalanceContext = ""
eCfg.FSEmptyBalanceAnnFile = ""
eCfg.FSCdrExtraFields = []*utils.RSRField{}
eCfg.OsipsListenUdp = "127.0.0.1:2020"
eCfg.OsipsMiAddr = "127.0.0.1:8020"
eCfg.OsipsEvSubscInterval = time.Duration(60) * time.Second
eCfg.OsipsReconnects = 3
eCfg.DerivedChargers = make(utils.DerivedChargers, 0)
eCfg.CombinedDerivedChargers = true
eCfg.HistoryAgentEnabled = false
eCfg.HistoryServer = "internal"
eCfg.HistoryServer = utils.INTERNAL
eCfg.HistoryServerEnabled = false
eCfg.HistoryDir = "/var/log/cgrates/history"
eCfg.HistorySaveInterval = time.Duration(1) * time.Second
@@ -146,28 +145,13 @@ func TestDefaults(t *testing.T) {
eCfg.MailerAuthUser = "cgrates"
eCfg.MailerAuthPass = "CGRateS.org"
eCfg.MailerFromAddr = "cgr-mailer@localhost.localdomain"
eCfg.CdreExportedFields = []*utils.RSRField{
&utils.RSRField{Id: utils.CGRID},
&utils.RSRField{Id: utils.MEDI_RUNID},
&utils.RSRField{Id: utils.ACCID},
&utils.RSRField{Id: utils.CDRHOST},
&utils.RSRField{Id: utils.REQTYPE},
&utils.RSRField{Id: utils.DIRECTION},
&utils.RSRField{Id: utils.TENANT},
&utils.RSRField{Id: utils.TOR},
&utils.RSRField{Id: utils.ACCOUNT},
&utils.RSRField{Id: utils.SUBJECT},
&utils.RSRField{Id: utils.DESTINATION},
&utils.RSRField{Id: utils.SETUP_TIME},
&utils.RSRField{Id: utils.ANSWER_TIME},
&utils.RSRField{Id: utils.DURATION},
&utils.RSRField{Id: utils.COST},
}
eCfg.DataFolderPath = "/usr/share/cgrates/"
if !reflect.DeepEqual(cfg, eCfg) {
t.Log(eCfg)
t.Log(cfg)
t.Error("Defaults different than expected!")
}
}
func TestSanityCheck(t *testing.T) {
@@ -179,21 +163,26 @@ func TestSanityCheck(t *testing.T) {
if err := cfg.checkConfigSanity(); err != nil {
t.Error("Invalid defaults: ", err)
}
cfg.SMSubjectFields = []string{"sample1", "sample2", "sample3"}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect config insanity")
}
cfg = &CGRConfig{}
cfg.CdreCdrFormat = utils.CDRE_FIXED_WIDTH
cfg.CdrcEnabled = true
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect fixed_width dependency on xml configuration")
t.Error("Failed to detect missing CDR fields definition")
}
cfg.CdrcCdrType = utils.CSV
cfg.CdrcCdrFields = map[string][]*utils.RSRField{utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}}}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect improper use of CDR field names")
}
cfg.CdrcCdrFields = map[string][]*utils.RSRField{"extra1": []*utils.RSRField{&utils.RSRField{Id: "test"}}}
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect improper use of CDR field names")
}
}
// Load config from file and make sure we have all set
func TestConfigFromFile(t *testing.T) {
cfgPth := "test_data.txt"
cfg, err := NewCGRConfig(&cfgPth)
cfg, err := NewCGRConfigFromFile(&cfgPth)
if err != nil {
t.Log(fmt.Sprintf("Could not parse config: %s!", err))
t.FailNow()
@@ -223,11 +212,11 @@ func TestConfigFromFile(t *testing.T) {
eCfg.RPCGOBListen = "test"
eCfg.HTTPListen = "test"
eCfg.DefaultReqType = "test"
eCfg.DefaultTOR = "test"
eCfg.DefaultCategory = "test"
eCfg.DefaultTenant = "test"
eCfg.DefaultSubject = "test"
eCfg.RoundingMethod = "test"
eCfg.RoundingDecimals = 99
eCfg.HttpSkipTlsVerify = true
eCfg.RaterEnabled = true
eCfg.RaterBalancer = "test"
eCfg.BalancerEnabled = true
@@ -235,63 +224,77 @@ func TestConfigFromFile(t *testing.T) {
eCfg.CDRSEnabled = true
eCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
eCfg.CDRSMediator = "test"
eCfg.CdreCdrFormat = "test"
eCfg.CdreExportedFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
eCfg.CdreDir = "test"
eCfg.CDRSStats = "test"
eCfg.CDRSStoreDisable = true
eCfg.CDRStatsEnabled = true
eCfg.CDRStatConfig = &CdrStatsConfig{Id: utils.META_DEFAULT, QueueLength: 99, TimeWindow: time.Duration(99) * time.Second,
Metrics: []string{"test"}, TORs: []string{"test"}, CdrHosts: []string{"test"}, CdrSources: []string{"test"}, ReqTypes: []string{"test"}, Directions: []string{"test"},
Tenants: []string{"test"}, Categories: []string{"test"}, Accounts: []string{"test"}, Subjects: []string{"test"}, DestinationPrefixes: []string{"test"},
UsageInterval: []time.Duration{time.Duration(99) * time.Second},
MediationRunIds: []string{"test"}, RatedAccounts: []string{"test"}, RatedSubjects: []string{"test"}, CostInterval: []float64{99.0}}
eCfg.CDRSStats = "test"
eCfg.CdreDefaultInstance = &CdreConfig{
CdrFormat: "test",
FieldSeparator: utils.CSV_SEP,
DataUsageMultiplyFactor: 99.0,
CostMultiplyFactor: 99.0,
CostRoundingDecimals: 99,
CostShiftDigits: 99,
MaskDestId: "test",
MaskLength: 99,
ExportDir: "test"}
eCfg.CdreDefaultInstance.ContentFields, _ = NewCdreCdrFieldsFromIds(false, "test")
eCfg.CdrcEnabled = true
eCfg.CdrcCdrs = "test"
eCfg.CdrcCdrsMethod = "test"
eCfg.CdrcRunDelay = time.Duration(99) * time.Second
eCfg.CdrcCdrType = "test"
eCfg.CdrcCsvSep = ";"
eCfg.CdrcCdrInDir = "test"
eCfg.CdrcCdrOutDir = "test"
eCfg.CdrcSourceId = "test"
eCfg.CdrcAccIdField = "test"
eCfg.CdrcReqTypeField = "test"
eCfg.CdrcDirectionField = "test"
eCfg.CdrcTenantField = "test"
eCfg.CdrcTorField = "test"
eCfg.CdrcAccountField = "test"
eCfg.CdrcSubjectField = "test"
eCfg.CdrcDestinationField = "test"
eCfg.CdrcSetupTimeField = "test"
eCfg.CdrcAnswerTimeField = "test"
eCfg.CdrcDurationField = "test"
eCfg.CdrcExtraFields = []string{"test"}
eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
"test": []*utils.RSRField{&utils.RSRField{Id: "test"}},
}
eCfg.MediatorEnabled = true
eCfg.MediatorRater = "test"
eCfg.MediatorRaterReconnects = 99
eCfg.MediatorRunIds = []string{"test"}
eCfg.MediatorSubjectFields = []string{"test"}
eCfg.MediatorReqTypeFields = []string{"test"}
eCfg.MediatorDirectionFields = []string{"test"}
eCfg.MediatorTenantFields = []string{"test"}
eCfg.MediatorTORFields = []string{"test"}
eCfg.MediatorAccountFields = []string{"test"}
eCfg.MediatorDestFields = []string{"test"}
eCfg.MediatorSetupTimeFields = []string{"test"}
eCfg.MediatorAnswerTimeFields = []string{"test"}
eCfg.MediatorDurationFields = []string{"test"}
eCfg.MediatorReconnects = 99
eCfg.MediatorStats = "test"
eCfg.MediatorStoreDisable = true
eCfg.SMEnabled = true
eCfg.SMSwitchType = "test"
eCfg.SMRater = "test"
eCfg.SMRaterReconnects = 99
eCfg.SMCdrS = "test"
eCfg.SMReconnects = 99
eCfg.SMDebitInterval = 99
eCfg.SMMinCallDuration = time.Duration(98) * time.Second
eCfg.SMMaxCallDuration = time.Duration(99) * time.Second
eCfg.SMRunIds = []string{"test"}
eCfg.SMReqTypeFields = []string{"test"}
eCfg.SMDirectionFields = []string{"test"}
eCfg.SMTenantFields = []string{"test"}
eCfg.SMTORFields = []string{"test"}
eCfg.SMAccountFields = []string{"test"}
eCfg.SMSubjectFields = []string{"test"}
eCfg.SMDestFields = []string{"test"}
eCfg.SMSetupTimeFields = []string{"test"}
eCfg.SMAnswerTimeFields = []string{"test"}
eCfg.SMDurationFields = []string{"test"}
eCfg.FreeswitchServer = "test"
eCfg.FreeswitchPass = "test"
eCfg.FreeswitchReconnects = 99
eCfg.FSMinDurLowBalance = time.Duration(99) * time.Second
eCfg.FSLowBalanceAnnFile = "test"
eCfg.FSEmptyBalanceContext = "test"
eCfg.FSEmptyBalanceAnnFile = "test"
eCfg.FSCdrExtraFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
eCfg.OsipsListenUdp = "test"
eCfg.OsipsMiAddr = "test"
eCfg.OsipsEvSubscInterval = time.Duration(99) * time.Second
eCfg.OsipsReconnects = 99
eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", RunFilters: "", ReqTypeField: "test", DirectionField: "test", TenantField: "test",
CategoryField: "test", AccountField: "test", SubjectField: "test", DestinationField: "test", SetupTimeField: "test", AnswerTimeField: "test", UsageField: "test"}}
eCfg.CombinedDerivedChargers = true
eCfg.HistoryAgentEnabled = true
eCfg.HistoryServer = "test"
eCfg.HistoryServerEnabled = true
@@ -301,9 +304,91 @@ func TestConfigFromFile(t *testing.T) {
eCfg.MailerAuthUser = "test"
eCfg.MailerAuthPass = "test"
eCfg.MailerFromAddr = "test"
eCfg.DataFolderPath = "/usr/share/cgrates/"
if !reflect.DeepEqual(cfg, eCfg) {
t.Log(eCfg)
t.Log(cfg)
t.Error("Loading of configuration from file failed!")
}
}
func TestCdrsExtraFields(t *testing.T) {
eFieldsCfg := []byte(`[cdrs]
extra_fields = extr1,extr2
`)
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CDRSExtraFields, []*utils.RSRField{&utils.RSRField{Id: "extr1"}, &utils.RSRField{Id: "extr2"}}) {
t.Errorf("Unexpected value for CdrsExtraFields: %v", cfg.CDRSExtraFields)
}
eFieldsCfg = []byte(`[cdrs]
extra_fields = ~effective_caller_id_number:s/(\d+)/+$1/
`)
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CDRSExtraFields, []*utils.RSRField{&utils.RSRField{Id: "effective_caller_id_number",
RSRules: []*utils.ReSearchReplace{&utils.ReSearchReplace{SearchRegexp: regexp.MustCompile(`(\d+)`), ReplaceTemplate: "+$1"}}}}) {
t.Errorf("Unexpected value for config CdrsExtraFields: %v", cfg.CDRSExtraFields)
}
eFieldsCfg = []byte(`[cdrs]
extra_fields = extr1,~extr2:s/x.+/
`)
if _, err := NewCGRConfigFromBytes(eFieldsCfg); err == nil {
t.Error("Failed to detect failed RSRParsing")
}
}
func TestCdreExtraFields(t *testing.T) {
eFieldsCfg := []byte(`[cdre]
cdr_format = csv
export_template = cgrid,mediation_runid,accid
`)
expectedFlds := []*CdreCdrField{
&CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Mandatory: true},
&CdreCdrField{Name: "mediation_runid", Type: utils.CDRFIELD, Value: "mediation_runid", valueAsRsrField: &utils.RSRField{Id: "mediation_runid"},
Mandatory: true},
&CdreCdrField{Name: "accid", Type: utils.CDRFIELD, Value: "accid", valueAsRsrField: &utils.RSRField{Id: "accid"},
Mandatory: true},
}
expCdreCfg := &CdreConfig{CdrFormat: utils.CSV, FieldSeparator: utils.CSV_SEP, CostRoundingDecimals: -1, ExportDir: "/var/log/cgrates/cdre", ContentFields: expectedFlds}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CdreDefaultInstance, expCdreCfg) {
t.Errorf("Expecting: %v, received: %v", expCdreCfg, cfg.CdreDefaultInstance)
}
eFieldsCfg = []byte(`[cdre]
cdr_format = csv
export_template = cgrid,~effective_caller_id_number:s/(\d+)/+$1/
`)
rsrField, _ := utils.NewRSRField(`~effective_caller_id_number:s/(\d+)/+$1/`)
expectedFlds = []*CdreCdrField{
&CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Mandatory: true},
&CdreCdrField{Name: `~effective_caller_id_number:s/(\d+)/+$1/`, Type: utils.CDRFIELD, Value: `~effective_caller_id_number:s/(\d+)/+$1/`,
valueAsRsrField: rsrField, Mandatory: false}}
expCdreCfg.ContentFields = expectedFlds
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CdreDefaultInstance, expCdreCfg) {
t.Errorf("Expecting: %v, received: %v", expCdreCfg, cfg.CdreDefaultInstance)
}
eFieldsCfg = []byte(`[cdre]
cdr_format = csv
export_template = cgrid,~accid:s/(\d)/$1,runid
`)
if _, err := NewCGRConfigFromBytes(eFieldsCfg); err == nil {
t.Error("Failed to detect failed RSRParsing")
}
}
func TestCdrcCdrDefaultFields(t *testing.T) {
cdrcCfg := []byte(`[cdrc]
enabled = true
`)
cfgDefault, _ := NewDefaultCGRConfig()
if cfg, err := NewCGRConfigFromBytes(cdrcCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, cfgDefault.CdrcCdrFields) {
t.Errorf("Unexpected value for CdrcCdrFields: %v", cfg.CdrcCdrFields)
}
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,46 +21,135 @@ package config
import (
"code.google.com/p/goconf/conf"
"errors"
"fmt"
"strings"
"github.com/cgrates/cgrates/utils"
)
// Adds support for slice values in config
func ConfigSlice(c *conf.ConfigFile, section, valName string) ([]string, error) {
sliceStr, errGet := c.GetString(section, valName)
if errGet != nil {
return nil, errGet
}
cfgValStrs := strings.Split(sliceStr, ",") // If need arrises, we can make the separator configurable
if len(cfgValStrs) == 1 && cfgValStrs[0] == "" { // Prevents returning iterable with empty value
return []string{}, nil
}
for _, elm := range cfgValStrs {
if elm == "" { //One empty element is presented when splitting empty string
return nil, errors.New("Empty values in config slice")
}
func ConfigSlice(cfgVal string) ([]string, error) {
cfgValStrs := strings.Split(cfgVal, utils.FIELDS_SEP) // If need arrises, we can make the separator configurable
for idx, elm := range cfgValStrs {
cfgValStrs[idx] = strings.TrimSpace(elm) // By default spaces are not removed so we do it here to avoid unpredicted results in config
}
return cfgValStrs, nil
}
func ParseRSRFields(configVal string) ([]*utils.RSRField, error) {
cfgValStrs := strings.Split(configVal, string(utils.CSV_SEP))
if len(cfgValStrs) == 1 && cfgValStrs[0] == "" { // Prevents returning iterable with empty value
return []*utils.RSRField{}, nil
// Parse the configuration file and returns utils.DerivedChargers instance if no errors
func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err error) {
var runIds, runFilters, reqTypeFlds, directionFlds, tenantFlds, torFlds, acntFlds, subjFlds, dstFlds, sTimeFlds, aTimeFlds, durFlds []string
cfgVal, _ := c.GetString("derived_charging", "run_ids")
if runIds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
rsrFields := make([]*utils.RSRField, len(cfgValStrs))
for idx, cfgValStr := range cfgValStrs {
if len(cfgValStr) == 0 { //One empty element is presented when splitting empty string
return nil, errors.New("Empty values in config slice")
cfgVal, _ = c.GetString("derived_charging", "run_filters")
if runFilters, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "reqtype_fields")
if reqTypeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "direction_fields")
if directionFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "tenant_fields")
if tenantFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "category_fields")
if torFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "account_fields")
if acntFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "subject_fields")
if subjFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "destination_fields")
if dstFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "setup_time_fields")
if sTimeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "answer_time_fields")
if aTimeFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
cfgVal, _ = c.GetString("derived_charging", "usage_fields")
if durFlds, err = ConfigSlice(cfgVal); err != nil {
return nil, err
}
// We need all to be the same length
if len(runFilters) != len(runIds) ||
len(reqTypeFlds) != len(runIds) ||
len(directionFlds) != len(runIds) ||
len(tenantFlds) != len(runIds) ||
len(torFlds) != len(runIds) ||
len(acntFlds) != len(runIds) ||
len(subjFlds) != len(runIds) ||
len(dstFlds) != len(runIds) ||
len(sTimeFlds) != len(runIds) ||
len(aTimeFlds) != len(runIds) ||
len(durFlds) != len(runIds) {
return nil, errors.New("<ConfigSanity> Inconsistent fields length in derivated_charging section")
}
// Create the individual chargers and append them to the final instance
dcs = make(utils.DerivedChargers, 0)
if len(runIds) == 1 && len(runIds[0]) == 0 { // Avoid iterating on empty runid
return dcs, nil
}
for runIdx, runId := range runIds {
dc, err := utils.NewDerivedCharger(runId, runFilters[runIdx], reqTypeFlds[runIdx], directionFlds[runIdx], tenantFlds[runIdx], torFlds[runIdx],
acntFlds[runIdx], subjFlds[runIdx], dstFlds[runIdx], sTimeFlds[runIdx], aTimeFlds[runIdx], durFlds[runIdx])
if err != nil {
return nil, err
}
if rsrField, err := utils.NewRSRField(cfgValStr); err != nil {
if dcs, err = dcs.Append(dc); err != nil {
return nil, err
}
}
return dcs, nil
}
func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string][]*utils.RSRField, error) {
cdrcCdrFlds := make(map[string][]*utils.RSRField)
if len(extraFlds) != 0 {
if sepExtraFlds, err := ConfigSlice(extraFlds); err != nil {
return nil, err
} else {
rsrFields[idx] = rsrField
for _, fldStr := range sepExtraFlds {
// extra fields defined as: <label_extrafield_1>:<index_extrafield_1>
if spltLbl := strings.Split(fldStr, utils.CONCATENATED_KEY_SEP); len(spltLbl) != 2 {
return nil, fmt.Errorf("Wrong format for cdrc.extra_fields: %s", fldStr)
} else {
if rsrFlds, err := utils.ParseRSRFields(spltLbl[1], utils.INFIELD_SEP); err != nil {
return nil, err
} else {
cdrcCdrFlds[spltLbl[0]] = rsrFlds
}
}
}
}
}
return rsrFields, nil
for fldTag, fldVal := range map[string]string{utils.TOR: torFld, utils.ACCID: accIdFld, utils.REQTYPE: reqtypeFld, utils.DIRECTION: directionFld, utils.TENANT: tenantFld,
utils.CATEGORY: categoryFld, utils.ACCOUNT: acntFld, utils.SUBJECT: subjectFld, utils.DESTINATION: destFld, utils.SETUP_TIME: setupTimeFld,
utils.ANSWER_TIME: answerTimeFld, utils.USAGE: durFld} {
if len(fldVal) != 0 {
if rsrFlds, err := utils.ParseRSRFields(fldVal, utils.INFIELD_SEP); err != nil {
return nil, err
} else {
cdrcCdrFlds[fldTag] = rsrFlds
}
}
}
return cdrcCdrFlds, nil
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,21 +19,122 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"encoding/json"
"reflect"
"regexp"
"testing"
"github.com/cgrates/cgrates/utils"
)
func TestParseRSRFields(t *testing.T) {
fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination`
expectParsedFields := []*utils.RSRField{&utils.RSRField{Id: "host"},
&utils.RSRField{Id: "sip_redirected_to", RSRule: &utils.ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)@`), "0$1"}},
&utils.RSRField{Id: "destination"}}
if parsedFields, err := ParseRSRFields(fields); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if !reflect.DeepEqual(parsedFields, expectParsedFields) {
t.Errorf("Unexpected value of parsed fields")
func TestConfigSlice(t *testing.T) {
eCS := []string{"", ""}
if cs, err := ConfigSlice(" , "); err != nil {
t.Error("Unexpected error: ", err)
} else if !reflect.DeepEqual(eCS, cs) {
t.Errorf("Expecting: %v, received: %v", eCS, cs)
}
}
func TestParseCfgDerivedCharging(t *testing.T) {
eFieldsCfg := []byte(`[derived_charging]
run_ids = run1, run2
run_filters =,
reqtype_fields = test1, test2
direction_fields = test1, test2
tenant_fields = test1, test2
category_fields = test1, test2
account_fields = test1, test2
subject_fields = test1, test2
destination_fields = test1, test2
setup_time_fields = test1, test2
answer_time_fields = test1, test2
usage_fields = test1, test2
`)
edcs := utils.DerivedChargers{
&utils.DerivedCharger{RunId: "run1", ReqTypeField: "test1", DirectionField: "test1", TenantField: "test1", CategoryField: "test1",
AccountField: "test1", SubjectField: "test1", DestinationField: "test1", SetupTimeField: "test1", AnswerTimeField: "test1", UsageField: "test1"},
&utils.DerivedCharger{RunId: "run2", ReqTypeField: "test2", DirectionField: "test2", TenantField: "test2", CategoryField: "test2",
AccountField: "test2", SubjectField: "test2", DestinationField: "test2", SetupTimeField: "test2", AnswerTimeField: "test2", UsageField: "test2"}}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.DerivedChargers, edcs) {
t.Errorf("Expecting: %v, received: %v", edcs, cfg.DerivedChargers)
}
}
func TestParseCfgDerivedChargingDn1(t *testing.T) {
eFieldsCfg := []byte(`[derived_charging]
run_ids = run1, run2
run_filters =~account:s/^\w+[mpls]\d{6}$//,~account:s/^0\d{9}$//;^account/value/
reqtype_fields = test1, test2
direction_fields = test1, test2
tenant_fields = test1, test2
category_fields = test1, test2
account_fields = test1, test2
subject_fields = test1, test2
destination_fields = test1, test2
setup_time_fields = test1, test2
answer_time_fields = test1, test2
usage_fields = test1, test2
`)
eDcs := make(utils.DerivedChargers, 2)
if dc, err := utils.NewDerivedCharger("run1", `~account:s/^\w+[mpls]\d{6}$//`, "test1", "test1", "test1",
"test1", "test1", "test1", "test1", "test1", "test1", "test1"); err != nil {
t.Error("Unexpected error: ", err)
} else {
eDcs[0] = dc
}
if dc, err := utils.NewDerivedCharger("run2", `~account:s/^0\d{9}$//;^account/value/`, "test2", "test2", "test2",
"test2", "test2", "test2", "test2", "test2", "test2", "test2"); err != nil {
t.Error("Unexpected error: ", err)
} else {
eDcs[1] = dc
}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.DerivedChargers, eDcs) {
dcsJson, _ := json.Marshal(cfg.DerivedChargers)
t.Errorf("Received: %s", string(dcsJson))
}
}
func TestParseCdrcCdrFields(t *testing.T) {
eFieldsCfg := []byte(`[cdrc]
cdr_type = test
tor_field = tor1
accid_field = accid1
reqtype_field = reqtype1
direction_field = direction1
tenant_field = tenant1
category_field = category1
account_field = account1
subject_field = subject1
destination_field = destination1
setup_time_field = setuptime1
answer_time_field = answertime1
usage_field = duration1
extra_fields = extra1:extraval1,extra2:extraval1
`)
eCdrcCdrFlds := map[string][]*utils.RSRField{
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "tor1"}},
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "accid1"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "reqtype1"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "direction1"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "tenant1"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "category1"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "account1"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "subject1"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "destination1"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "setuptime1"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "answertime1"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "duration1"}},
"extra1": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
"extra2": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
}
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
t.Error("Could not parse the config", err.Error())
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, eCdrcCdrFlds) {
t.Errorf("Expecting: %v, received: %v, tor: %v", eCdrcCdrFlds, cfg.CdrcCdrFields, cfg.CdrcCdrFields[utils.TOR])
}
}

View File

@@ -14,23 +14,22 @@ accountdb_port = test # Accounting subsystem port to reach the database.
accountdb_name = test # Accounting subsystem database name to connect to.
accountdb_user = test # Accounting subsystem username to use when connecting to database.
accountdb_passwd = test # Accounting subsystem password to use when connecting to database.
stordb_type = test # Log/stored database type to use: <same|postgres|mongo|redis>
stordb_type = test # Log/scategoryed database type to use: <same|postgres|mongo|redis>
stordb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
stordb_port = test # The port to reach the logdb.
stordb_name = test # The name of the log database to connect to.
stordb_user = test # Username to use when connecting to logdb.
stordb_passwd = test # Password to use when connecting to logdb.
dbdata_encoding = test # The encoding used to store object data in strings: <msgpack|json>
dbdata_encoding = test # The encoding used to scategorye object data in strings: <msgpack|json>
rpc_json_listen = test # RPC JSON listening address
rpc_gob_listen = test # RPC GOB listening address
http_listen = test # HTTP listening address
default_reqtype = test # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
default_tor = test # Default Type of Record to consider when missing from requests.
default_category = test # Default Type of Record to consider when missing from requests.
default_tenant = test # Default Tenant to consider when missing from requests.
default_subject = test # Default rating Subject to consider when missing from requests.
rounding_method = test # Rounding method for floats/costs: <up|middle|down>
rounding_decimals = 99 # Number of decimals to round floats/costs at
http_skip_tls_veify = true # If enabled Http Client will accept any TLS certificate
[balancer]
enabled = true # Start Balancer service: <true|false>.
@@ -44,84 +43,124 @@ enabled = true # Starts Scheduler service: <true|false>.
[cdrs]
enabled = true # Start the CDR Server service: <true|false>.
extra_fields = test # Extra fields to store in CDRs
extra_fields = test # Extra fields to scategorye in CDRs
mediator = test # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
cdrstats = test # Address where to reach the CDRStats server. Empty for disabling stats. <""|internal>
store_disable = true # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
[cdre]
cdr_format = test # Exported CDRs format <csv>
export_dir = test # Path where the exported CDRs will be placed
export_template = test # List of fields in the exported CDRs
cdr_format = test # Exported CDRs format <csv>
data_usage_multiply_factor = 99.0 # Multiply data usage before export (eg: convert from KBytes to Bytes)
cost_multiply_factor = 99.0 # Multiply cost before export (0.0 to disable), eg: add VAT
cost_rounding_decimals = 99 # Rounding decimals for Cost values. -1 to disable rounding
cost_shift_digits = 99 # Shift digits in the cost on export (eg: convert from EUR to cents)
mask_destination_id = test # Destination id containing called addresses to be masked on export
mask_length = 99 # Length of the destination suffix to be masked
export_dir = test # Path where the exported CDRs will be placed
export_template = test # List of fields in the exported CDRs
[cdrc]
enabled = true # Enable CDR client functionality
cdrs = test # Address where to reach CDR server
cdrs_method = test # Mechanism to use when posting CDRs on server <http_cgr>
cdrs = test # Address where to reach CDR server
run_delay = 99 # Period to sleep between two runs, 0 to use automation via inotify
cdr_type = test # CDR file format <csv>.
cdr_in_dir = test # Absolute path towards the directory where the CDRs are kept (file stored CDRs).
cdr_out_dir = test # Absolute path towards the directory where processed CDRs will be moved after processing.
cdr_source_id = test # Tag identifying the source of the CDRs within CGRS database.
csv_separator =; # Csv separator, one character only and should be next to equal sign
cdr_in_dir = test # Absolute path towards the direccategoryy where the CDRs are kept (file scategoryed CDRs).
cdr_out_dir = test # Absolute path towards the direccategoryy where processed CDRs will be moved after processing.
cdr_source_id = test # Tag identifying the source of the CDRs within CGRS database.
tor_field = test # TypeOfRecord field identifier. Use index number in case of .csv cdrs.
accid_field = test # Accounting id field identifier. Use index number in case of .csv cdrs.
reqtype_field = test # Request type field identifier. Use index number in case of .csv cdrs.
direction_field = test # Direction field identifier. Use index numbers in case of .csv cdrs.
reqtype_field = test # Request type field identifier. Use index number in case of .csv cdrs.
direction_field = test # Direction field identifier. Use index numbers in case of .csv cdrs.
tenant_field = test # Tenant field identifier. Use index numbers in case of .csv cdrs.
tor_field = test # Type of Record field identifier. Use index numbers in case of .csv cdrs.
account_field = test # Account field identifier. Use index numbers in case of .csv cdrs.
subject_field = test # Subject field identifier. Use index numbers in case of .csv CDRs.
destination_field = test # Destination field identifier. Use index numbers in case of .csv cdrs.
setup_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
answer_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
duration_field = test # Duration field identifier. Use index numbers in case of .csv cdrs.
extra_fields = test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1:field1,index2:field2"
category_field = test # Type of Record field identifier. Use index numbers in case of .csv cdrs.
account_field = test # Account field identifier. Use index numbers in case of .csv cdrs.
subject_field = test # Subject field identifier. Use index numbers in case of .csv CDRs.
destination_field = test # Destination field identifier. Use index numbers in case of .csv cdrs.
setup_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
answer_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
usage_field = test # Duration field identifier. Use index numbers in case of .csv cdrs.
extra_fields = test:test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1|field1,index2|field2"
[mediator]
enabled = true # Starts Mediator service: <true|false>.
enabled = true # Starts Mediacategory service: <true|false>.
rater = test # Address where to reach the Rater: <internal|x.y.z.y:1234>
rater_reconnects = 99 # Number of reconnects to rater before giving up.
run_ids = test # Identifiers for each mediation run on CDRs
subject_fields = test # Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
reqtype_fields = test # Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
direction_fields = test # Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
tenant_fields = test # Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
tor_fields = test # Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
account_fields = test # Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
destination_fields = test # Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
setup_time_fields = test # Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
answer_time_fields = test # Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
duration_fields = test # Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
reconnects = 99 # Number of reconnects to rater before giving up.
cdrstats = test # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
store_disable = true # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
[cdrstats]
enabled = true # Start the CDR stats service: <true|false>.
queue_length = 99 # Number of items in the stats buffer
time_window = 99 # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
metrics = test # Stat metric ids to build
setup_interval = # Filter on CDR SetupTime
tors = test # Filter on CDR TOR fields
cdr_hosts= test # Filter on CDR CdrHost fields
cdr_sources = test # Filter on CDR CdrSource fields
req_types = test # Filter on CDR ReqType fields
directions = test # Filter on CDR Direction fields
tenants = test # Filter on CDR Tenant fields
categories = test # Filter on CDR Category fields
accounts = test # Filter on CDR Account fields
subjects = test # Filter on CDR Subject fields
destination_prefixes = test # Filter on CDR Destination prefixes
usage_interval = 99 # Filter on CDR Usage
mediation_run_ids = test # Filter on CDR MediationRunId fields
rated_accounts = test # Filter on CDR RatedAccount fields
rated_subjects = test # Filter on CDR RatedSubject fields
cost_intervals = 99 # Filter on CDR Cost
[session_manager]
enabled = true # Starts SessionManager service: <true|false>.
switch_type = test # Defines the type of switch behind: <freeswitch>.
rater = test # Address where to reach the Rater.
rater_reconnects = 99 # Number of reconnects to rater before giving up.
cdrs = test # Address where to reach CDR Server, empty to disable CDR capturing <""|internal|127.0.0.1:2013>
reconnects = 99 # Number of reconnects to rater before giving up.
debit_interval = 99 # Interval to perform debits on.
max_call_duration = 99 # Maximum call duration a prepaid call can last
run_ids = test # Identifiers of additional sessions control.
reqtype_fields = test # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
direction_fields = test # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
tenant_fields = test # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
tor_fields = test # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
account_fields = test # Name of account fields to be used during additional sessions control <""|*default|field_name>.
subject_fields = test # Name of fields to be used during additional sessions control <""|*default|field_name>.
destination_fields = test # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
setup_time_fields = test # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
answer_time_fields = test # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
duration_fields = test # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
min_call_duration = 98 # Only authorize calls with allowed duration bigger than this
max_call_duration = 99 # Maximum call duration a prepaid call can last
[freeswitch]
server = test # Adress where to connect to FreeSWITCH socket.
passwd = test # FreeSWITCH socket password.
reconnects = 99 # Number of attempts on connect failure.
server = test # Adress where to connect to FreeSWITCH socket.
passwd = test # FreeSWITCH socket password.
reconnects = 99 # Number of attempts on connect failure.
min_dur_low_balance = 99 # Threshold which will trigger low balance warnings
low_balance_ann_file = test # File to be played when low balance is reached
empty_balance_context = test # If defined, call will be transfered to this context on empty balance
empty_balance_ann_file = test # File to be played before disconnecting prepaid calls (applies only if no context defined)
cdr_extra_fields = test # Extra fields to store in CDRs in case of processing them
[opensips]
listen_udp = test # Address where to listen for event datagrams coming from OpenSIPS
mi_addr = test # Adress where to reach OpenSIPS mi_datagram module
events_subscribe_interval = 99 # Automatic events subscription to OpenSIPS, 0 to disable it
cdrs = test # Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013>
reconnects = 99 # Number of attempts on connect failure.
[derived_charging]
run_ids = test # Identifiers of additional sessions control.
run_filters = # No filters applied
reqtype_fields = test # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
direction_fields = test # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
tenant_fields = test # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
category_fields = test # Name of category fields to be used during additional sessions control <""|*default|field_name>.
account_fields = test # Name of account fields to be used during additional sessions control <""|*default|field_name>.
subject_fields = test # Name of fields to be used during additional sessions control <""|*default|field_name>.
destination_fields = test # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
setup_time_fields = test # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
answer_time_fields = test # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
usage_fields = test # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
[history_server]
enabled = true # Starts History service: <true|false>.
history_dir = test # Location on disk where to store history files.
enabled = true # Starts Hiscategoryy service: <true|false>.
history_dir = test # Location on disk where to scategorye hiscategoryy files.
save_interval = 99 # Timeout duration between saves
[history_agent]
enabled = true # Starts History as a client: <true|false>.
server = test # Address where to reach the master history server: <internal|x.y.z.y:1234>
enabled = true # Starts Hiscategoryy as a client: <true|false>.
server = test # Address where to reach the master hiscategoryy server: <internal|x.y.z.y:1234>
[mailer]
server = test # The server to use when sending emails out

87
config/xmlcdrc.go Normal file
View File

@@ -0,0 +1,87 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"encoding/xml"
"github.com/cgrates/cgrates/utils"
)
type CgrXmlCdrcCfg struct {
Enabled bool `xml:"enabled"` // Enable/Disable the
CdrsAddress string `xml:"cdrs_address"` // The address where CDRs can be reached
CdrType string `xml:"cdr_type"` // The type of CDR to process <csv>
CsvSeparator string `xml:"field_separator"` // The separator to use when reading csvs
RunDelay int64 `xml:"run_delay"` // Delay between runs
CdrInDir string `xml:"cdr_in_dir"` // Folder to process CDRs from
CdrOutDir string `xml:"cdr_out_dir"` // Folder to move processed CDRs to
CdrSourceId string `xml:"cdr_source_id"` // Source identifier for the processed CDRs
CdrFields []*CdrcField `xml:"fields>field"`
}
// Set the defaults
func (cdrcCfg *CgrXmlCdrcCfg) setDefaults() error {
dfCfg, _ := NewDefaultCGRConfig()
if len(cdrcCfg.CdrsAddress) == 0 {
cdrcCfg.CdrsAddress = dfCfg.CdrcCdrs
}
if len(cdrcCfg.CdrType) == 0 {
cdrcCfg.CdrType = dfCfg.CdrcCdrType
}
if len(cdrcCfg.CsvSeparator) == 0 {
cdrcCfg.CsvSeparator = dfCfg.CdrcCsvSep
}
if len(cdrcCfg.CdrInDir) == 0 {
cdrcCfg.CdrInDir = dfCfg.CdrcCdrInDir
}
if len(cdrcCfg.CdrOutDir) == 0 {
cdrcCfg.CdrOutDir = dfCfg.CdrcCdrOutDir
}
if len(cdrcCfg.CdrSourceId) == 0 {
cdrcCfg.CdrSourceId = dfCfg.CdrcSourceId
}
if len(cdrcCfg.CdrFields) == 0 {
for key, cfgRsrFields := range dfCfg.CdrcCdrFields {
cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: "PLACEHOLDER", rsrFields: cfgRsrFields})
}
}
return nil
}
func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string][]*utils.RSRField {
rsrFields := make(map[string][]*utils.RSRField)
for _, fld := range cdrcCfg.CdrFields {
rsrFields[fld.Id] = fld.rsrFields
}
return rsrFields
}
type CdrcField struct {
XMLName xml.Name `xml:"field"`
Id string `xml:"id,attr"`
Value string `xml:"value,attr"`
rsrFields []*utils.RSRField
}
func (cdrcFld *CdrcField) PopulateRSRFields() (err error) {
if cdrcFld.rsrFields, err = utils.ParseRSRFields(cdrcFld.Value, utils.INFIELD_SEP); err != nil {
return err
}
return nil
}

171
config/xmlcdrc_test.go Normal file
View File

@@ -0,0 +1,171 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"encoding/xml"
"github.com/cgrates/cgrates/utils"
"reflect"
"strings"
"testing"
)
var cfgDocCdrc *CgrXmlCfgDocument // Will be populated by first test
func TestPopulateRSRFIeld(t *testing.T) {
cdrcField := CdrcField{Id: "TEST1", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
if err := cdrcField.PopulateRSRFields(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdrcField.rsrFields == nil {
t.Error("Failed loading the RSRField")
}
cdrcField = CdrcField{Id: "TEST2", Value: `99`}
if err := cdrcField.PopulateRSRFields(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdrcField.rsrFields == nil {
t.Error("Failed loading the RSRField")
}
}
func TestSetDefaults(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdrc" id="CDRC-CSVDF">
<enabled>true</enabled>
</configuration>
</document>`
var xmlCdrc *CgrXmlCdrcCfg
reader := strings.NewReader(cfgXmlStr)
if cfgDocCdrcDf, err := ParseCgrXmlConfig(reader); err != nil {
t.Error(err.Error())
} else if cfgDocCdrcDf == nil {
t.Fatal("Could not parse xml configuration document")
} else if len(cfgDocCdrcDf.cdrcs) != 1 {
t.Error("Did not load cdrc")
} else {
xmlCdrc = cfgDocCdrcDf.cdrcs["CDRC-CSVDF"]
}
dfCfg, _ := NewDefaultCGRConfig()
xmlCdrc.setDefaults()
if xmlCdrc.CdrsAddress != dfCfg.CdrcCdrs ||
xmlCdrc.CdrType != dfCfg.CdrcCdrType ||
xmlCdrc.CsvSeparator != dfCfg.CdrcCsvSep ||
xmlCdrc.CdrInDir != dfCfg.CdrcCdrInDir ||
xmlCdrc.CdrOutDir != dfCfg.CdrcCdrOutDir ||
xmlCdrc.CdrSourceId != dfCfg.CdrcSourceId ||
len(xmlCdrc.CdrFields) != len(dfCfg.CdrcCdrFields) {
t.Error("Failed loading default configuration")
}
}
func TestParseXmlCdrcConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdrc" type="csv" id="CDRC-CSV1">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<csv_separator>,</csv_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/var/log/cgrates/cdrc/in</cdr_in_dir>
<cdr_out_dir>/var/log/cgrates/cdrc/out</cdr_out_dir>
<cdr_source_id>freeswitch_csv</cdr_source_id>
<fields>
<field id="accid" value="0;13" />
<field id="reqtype" value="1" />
<field id="direction" value="2" />
<field id="tenant" value="3" />
<field id="category" value="4" />
<field id="account" value="5" />
<field id="subject" value="6" />
<field id="destination" value="7" />
<field id="setup_time" value="8" />
<field id="answer_time" value="9" />
<field id="usage" value="10" />
<field id="extr1" value="11" />
<field id="extr2" value="12" />
</fields>
</configuration>
</document>`
var err error
reader := strings.NewReader(cfgXmlStr)
if cfgDocCdrc, err = ParseCgrXmlConfig(reader); err != nil {
t.Error(err.Error())
} else if cfgDocCdrc == nil {
t.Fatal("Could not parse xml configuration document")
}
if len(cfgDocCdrc.cdrcs) != 1 {
t.Error("Did not cache")
}
}
func TestGetCdrcCfgs(t *testing.T) {
cdrcfgs := cfgDocCdrc.GetCdrcCfgs("CDRC-CSV1")
if cdrcfgs == nil {
t.Error("No config instance returned")
}
expectCdrc := &CgrXmlCdrcCfg{Enabled: true, CdrsAddress: "internal", CdrType: "csv", CsvSeparator: ",",
RunDelay: 0, CdrInDir: "/var/log/cgrates/cdrc/in", CdrOutDir: "/var/log/cgrates/cdrc/out", CdrSourceId: "freeswitch_csv"}
cdrFlds := []*CdrcField{
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0;13"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.REQTYPE, Value: "1"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DIRECTION, Value: "2"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.TENANT, Value: "3"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.CATEGORY, Value: "4"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCOUNT, Value: "5"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SUBJECT, Value: "6"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DESTINATION, Value: "7"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SETUP_TIME, Value: "8"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ANSWER_TIME, Value: "9"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.USAGE, Value: "10"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr1", Value: "11"},
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr2", Value: "12"}}
for _, fld := range cdrFlds {
fld.PopulateRSRFields()
}
expectCdrc.CdrFields = cdrFlds
if !reflect.DeepEqual(expectCdrc, cdrcfgs["CDRC-CSV1"]) {
t.Errorf("Expecting: %v, received: %v", expectCdrc, cdrcfgs["CDRC-CSV1"])
}
}
func TestCdrRSRFields(t *testing.T) {
cdrcfgs := cfgDocCdrc.GetCdrcCfgs("CDRC-CSV1")
if cdrcfgs == nil {
t.Error("No config instance returned")
}
eRSRFields := map[string][]*utils.RSRField{
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "0"}, &utils.RSRField{Id: "13"}},
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "1"}},
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "2"}},
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "3"}},
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "4"}},
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "5"}},
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "7"}},
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "8"}},
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "9"}},
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "10"}},
"extr1": []*utils.RSRField{&utils.RSRField{Id: "11"}},
"extr2": []*utils.RSRField{&utils.RSRField{Id: "12"}},
}
if rsrFields := cdrcfgs["CDRC-CSV1"].CdrRSRFields(); !reflect.DeepEqual(rsrFields, eRSRFields) {
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFields)
}
}

154
config/xmlcdre.go Normal file
View File

@@ -0,0 +1,154 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"encoding/xml"
"github.com/cgrates/cgrates/utils"
)
// The CdrExporter configuration instance
type CgrXmlCdreCfg struct {
CdrFormat *string `xml:"cdr_format"`
FieldSeparator *string `xml:"field_separator"`
DataUsageMultiplyFactor *float64 `xml:"data_usage_multiply_factor"`
CostMultiplyFactor *float64 `xml:"cost_multiply_factor"`
CostRoundingDecimals *int `xml:"cost_rounding_decimals"`
CostShiftDigits *int `xml:"cost_shift_digits"`
MaskDestId *string `xml:"mask_destination_id"`
MaskLength *int `xml:"mask_length"`
ExportDir *string `xml:"export_dir"`
Header *CgrXmlCfgCdrHeader `xml:"export_template>header"`
Content *CgrXmlCfgCdrContent `xml:"export_template>content"`
Trailer *CgrXmlCfgCdrTrailer `xml:"export_template>trailer"`
}
func (xmlCdreCfg *CgrXmlCdreCfg) AsCdreConfig() *CdreConfig {
cdreCfg, _ := NewDefaultCdreConfig()
if xmlCdreCfg.CdrFormat != nil {
cdreCfg.CdrFormat = *xmlCdreCfg.CdrFormat
}
if xmlCdreCfg.FieldSeparator != nil && len(*xmlCdreCfg.FieldSeparator) == 1 {
sepStr := *xmlCdreCfg.FieldSeparator
cdreCfg.FieldSeparator = rune(sepStr[0])
}
if xmlCdreCfg.DataUsageMultiplyFactor != nil {
cdreCfg.DataUsageMultiplyFactor = *xmlCdreCfg.DataUsageMultiplyFactor
}
if xmlCdreCfg.CostMultiplyFactor != nil {
cdreCfg.CostMultiplyFactor = *xmlCdreCfg.CostMultiplyFactor
}
if xmlCdreCfg.CostRoundingDecimals != nil {
cdreCfg.CostRoundingDecimals = *xmlCdreCfg.CostRoundingDecimals
}
if xmlCdreCfg.CostShiftDigits != nil {
cdreCfg.CostShiftDigits = *xmlCdreCfg.CostShiftDigits
}
if xmlCdreCfg.MaskDestId != nil {
cdreCfg.MaskDestId = *xmlCdreCfg.MaskDestId
}
if xmlCdreCfg.MaskLength != nil {
cdreCfg.MaskLength = *xmlCdreCfg.MaskLength
}
if xmlCdreCfg.ExportDir != nil {
cdreCfg.ExportDir = *xmlCdreCfg.ExportDir
}
if xmlCdreCfg.Header != nil {
cdreCfg.HeaderFields = make([]*CdreCdrField, len(xmlCdreCfg.Header.Fields))
for idx, xmlFld := range xmlCdreCfg.Header.Fields {
cdreCfg.HeaderFields[idx] = xmlFld.AsCdreCdrField()
}
}
if xmlCdreCfg.Content != nil {
cdreCfg.ContentFields = make([]*CdreCdrField, len(xmlCdreCfg.Content.Fields))
for idx, xmlFld := range xmlCdreCfg.Content.Fields {
cdreCfg.ContentFields[idx] = xmlFld.AsCdreCdrField()
}
}
if xmlCdreCfg.Trailer != nil {
cdreCfg.TrailerFields = make([]*CdreCdrField, len(xmlCdreCfg.Trailer.Fields))
for idx, xmlFld := range xmlCdreCfg.Trailer.Fields {
cdreCfg.TrailerFields[idx] = xmlFld.AsCdreCdrField()
}
}
return cdreCfg
}
// CDR header
type CgrXmlCfgCdrHeader struct {
XMLName xml.Name `xml:"header"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR content
type CgrXmlCfgCdrContent struct {
XMLName xml.Name `xml:"content"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR trailer
type CgrXmlCfgCdrTrailer struct {
XMLName xml.Name `xml:"trailer"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR field
type CgrXmlCfgCdrField struct {
XMLName xml.Name `xml:"field"`
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Value string `xml:"value,attr"`
Width int `xml:"width,attr"` // Field width
Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright>
Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right>
Layout string `xml:"layout,attr"` // Eg. time format layout
Filter string `xml:"filter,attr"` // Eg. combimed filters
Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported
valueAsRsrField *utils.RSRField // Cached if the need arrises
filterAsRsrField *utils.RSRField
}
func (cdrFld *CgrXmlCfgCdrField) populateRSRField() (err error) {
cdrFld.valueAsRsrField, err = utils.NewRSRField(cdrFld.Value)
return err
}
func (cdrFld *CgrXmlCfgCdrField) populateFltrRSRField() (err error) {
cdrFld.filterAsRsrField, err = utils.NewRSRField(cdrFld.Filter)
return err
}
func (cdrFld *CgrXmlCfgCdrField) ValueAsRSRField() *utils.RSRField {
return cdrFld.valueAsRsrField
}
func (cdrFld *CgrXmlCfgCdrField) AsCdreCdrField() *CdreCdrField {
return &CdreCdrField{
Name: cdrFld.Name,
Type: cdrFld.Type,
Value: cdrFld.Value,
Width: cdrFld.Width,
Strip: cdrFld.Strip,
Padding: cdrFld.Padding,
Layout: cdrFld.Layout,
Filter: cdrFld.filterAsRsrField,
Mandatory: cdrFld.Mandatory,
valueAsRsrField: cdrFld.valueAsRsrField,
}
}

306
config/xmlcdre_test.go Normal file
View File

@@ -0,0 +1,306 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"fmt"
"github.com/cgrates/cgrates/utils"
"reflect"
"strings"
"testing"
)
var cfgDoc *CgrXmlCfgDocument // Will be populated by first test
func TestXmlCdreCfgPopulateCdreRSRFIeld(t *testing.T) {
cdreField := CgrXmlCfgCdrField{Name: "TEST1", Type: "cdrfield", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
if err := cdreField.populateRSRField(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdreField.valueAsRsrField == nil {
t.Error("Failed loading the RSRField")
}
valRSRField, _ := utils.NewRSRField(`~effective_caller_id_number:s/(\d+)/+$1/`)
if recv := cdreField.ValueAsRSRField(); !reflect.DeepEqual(valRSRField, recv) {
t.Errorf("Expecting %v, received %v", valRSRField, recv)
}
/*cdreField = CgrXmlCfgCdrField{Name: "TEST1", Type: "constant", Value: `someval`}
if err := cdreField.populateRSRField(); err != nil {
t.Error("Unexpected error: ", err.Error())
} else if cdreField.valueAsRsrField != nil {
t.Error("Should not load the RSRField")
}*/
}
func TestXmlCdreCfgParseXmlConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdre" id="CDRE-FW1">
<cdr_format>fwv</cdr_format>
<data_usage_multiply_factor>0.0</data_usage_multiply_factor>
<cost_multiply_factor>0.0</cost_multiply_factor>
<cost_rounding_decimals>-1</cost_rounding_decimals>
<cost_shift_digits>0</cost_shift_digits>
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
<mask_length>0</mask_length>
<export_dir>/var/log/cgrates/cdre</export_dir>
<export_template>
<header>
<fields>
<field name="TypeOfRecord" type="constant" value="10" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="DistributorCode" type="constant" value="VOI" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150400" width="12" />
<field name="Version" type="constant" value="01" width="2" />
<field name="Filler2" type="filler" width="105" />
</fields>
</header>
<content>
<fields>
<field name="TypeOfRecord" type="constant" value="20" width="2" />
<field name="Account" type="cdrfield" value="cgrid" width="12" mandatory="true" />
<field name="Subject" type="cdrfield" value="subject" strip="left" padding="left" width="5" />
<field name="CLI" type="cdrfield" value="cli" strip="xright" width="15" />
<field name="Destination" type="cdrfield" value="destination" strip="xright" width="24" />
<field name="TOR" type="constant" value="02" width="2" />
<field name="SubtypeTOR" type="constant" value="11" width="4" />
<field name="SetupTime" type="cdrfield" value="start_time" layout="020106150400" width="12" />
<field name="Duration" type="cdrfield" value="duration" width="6" multiply_factor_voice="1000" />
<field name="DataVolume" type="filler" width="6" />
<field name="TaxCode" type="constant" value="1" width="1" />
<field name="OperatorCode" type="cdrfield" value="operator" width="2" />
<field name="ProductId" type="cdrfield" value="productid" width="5" />
<field name="NetworkId" type="constant" value="3" width="1" />
<field name="CallId" type="cdrfield" value="accid" width="16" />
<field name="Filler" type="filler" width="8" />
<field name="Filler" type="filler" width="8" />
<field name="TerminationCode" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\s\s\s\s\s)&quot;/$1/" width="5" />
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
<field name="CalledMask" type="cdrfield" value="calledmask" width="1" />
</fields>
</content>
<trailer>
<fields>
<field name="TypeOfRecord" type="constant" value="90" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="DistributorCode" type="constant" value="VOI" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="NumberOfRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
<field name="CdrsDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
<field name="FirstCdrTime" type="metatag" value="first_cdr_time" layout="020106150400" width="12" />
<field name="LastCdrTime" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
<field name="Filler1" type="filler" width="93" />
</fields>
</trailer>
</export_template>
</configuration>
<configuration section="cdre" type="csv" id="CHECK-CSV1">
<export_template>
<content>
<fields>
<field name="CGRID" type="cdrfield" value="cgrid" width="40"/>
<field name="RatingSubject" type="cdrfield" value="subject" width="24" padding="left" strip="xright" mandatory="true"/>
<field name="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true"/>
<field name="AccountReference" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
<field name="AccountType" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
<field name="MultipleMed1" type="combimed" value="cost" strip="xright" padding="left" mandatory="true" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
</fields>
</content>
</export_template>
</configuration>
</document>`
var err error
reader := strings.NewReader(cfgXmlStr)
if cfgDoc, err = ParseCgrXmlConfig(reader); err != nil {
t.Error(err.Error())
} else if cfgDoc == nil {
t.Fatal("Could not parse xml configuration document")
}
if len(cfgDoc.cdres) != 2 {
t.Error("Did not cache")
}
}
func TestXmlCdreCfgGetCdreCfg(t *testing.T) {
cdreFWCfg := cfgDoc.GetCdreCfgs("CDRE-FW1")
if cdreFWCfg == nil {
t.Error("Could not parse CdreFw instance")
}
if len(cdreFWCfg["CDRE-FW1"].Header.Fields) != 8 {
t.Error("Unexpected number of header fields parsed", len(cdreFWCfg["CDRE-FW1"].Header.Fields))
}
if len(cdreFWCfg["CDRE-FW1"].Content.Fields) != 20 {
t.Error("Unexpected number of content fields parsed", len(cdreFWCfg["CDRE-FW1"].Content.Fields))
}
if len(cdreFWCfg["CDRE-FW1"].Trailer.Fields) != 9 {
t.Error("Unexpected number of trailer fields parsed", len(cdreFWCfg["CDRE-FW1"].Trailer.Fields))
}
cdreCsvCfg1 := cfgDoc.GetCdreCfgs("CHECK-CSV1")
if cdreCsvCfg1 == nil {
t.Error("Could not parse CdreFw instance")
}
if len(cdreCsvCfg1["CHECK-CSV1"].Content.Fields) != 6 {
t.Error("Unexpected number of content fields parsed", len(cdreCsvCfg1["CHECK-CSV1"].Content.Fields))
}
}
func TestXmlCdreCfgAsCdreConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdre" type="fixed_width" id="CDRE-FW2">
<cdr_format>fwv</cdr_format>
<field_separator>;</field_separator>
<data_usage_multiply_factor>1024.0</data_usage_multiply_factor>
<cost_multiply_factor>1.19</cost_multiply_factor>
<cost_rounding_decimals>-1</cost_rounding_decimals>
<cost_shift_digits>-3</cost_shift_digits>
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
<mask_length>1</mask_length>
<export_dir>/var/log/cgrates/cdre</export_dir>
<export_template>
<header>
<fields>
<field name="TypeOfRecord" type="constant" value="10" width="2" />
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
</fields>
</header>
<content>
<fields>
<field name="OperatorCode" type="cdrfield" value="operator" width="2" />
<field name="ProductId" type="cdrfield" value="productid" width="5" />
<field name="NetworkId" type="constant" value="3" width="1" />
<field name="FromHttpPost1" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" />
<field name="CombiMed1" type="combimed" value="cost" width="10" strip="xright" padding="left" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
</fields>
</content>
<trailer>
<fields>
<field name="DistributorCode" type="constant" value="VOI" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
</fields>
</trailer>
</export_template>
</configuration>
</document>`
var err error
reader := strings.NewReader(cfgXmlStr)
if cfgDoc, err = ParseCgrXmlConfig(reader); err != nil {
t.Error(err.Error())
} else if cfgDoc == nil {
t.Fatal("Could not parse xml configuration document")
}
xmlCdreCfgs := cfgDoc.GetCdreCfgs("CDRE-FW2")
if xmlCdreCfgs == nil {
t.Error("Could not parse XmlCdre instance")
}
eCdreCfg := &CdreConfig{
CdrFormat: "fwv",
FieldSeparator: ';',
DataUsageMultiplyFactor: 1024.0,
CostMultiplyFactor: 1.19,
CostRoundingDecimals: -1,
CostShiftDigits: -3,
MaskDestId: "MASKED_DESTINATIONS",
MaskLength: 1,
ExportDir: "/var/log/cgrates/cdre",
}
fltrCombiMed, _ := utils.NewRSRField("~mediation_runid:s/DEFAULT/SECOND_RUN/")
eCdreCfg.HeaderFields = []*CdreCdrField{
&CdreCdrField{
Name: "TypeOfRecord",
Type: "constant",
Value: "10",
Width: 2,
valueAsRsrField: &utils.RSRField{Id: "10"}},
&CdreCdrField{
Name: "LastCdr",
Type: "metatag",
Value: "last_cdr_time",
Layout: "020106150400",
Width: 12,
valueAsRsrField: &utils.RSRField{Id: "last_cdr_time"}},
}
eCdreCfg.ContentFields = []*CdreCdrField{
&CdreCdrField{
Name: "OperatorCode",
Type: "cdrfield",
Value: "operator",
Width: 2,
valueAsRsrField: &utils.RSRField{Id: "operator"},
},
&CdreCdrField{
Name: "ProductId",
Type: "cdrfield",
Value: "productid",
Width: 5,
valueAsRsrField: &utils.RSRField{Id: "productid"},
},
&CdreCdrField{
Name: "NetworkId",
Type: "constant",
Value: "3",
Width: 1,
valueAsRsrField: &utils.RSRField{Id: "3"},
},
&CdreCdrField{
Name: "FromHttpPost1",
Type: "http_post",
Value: "https://localhost:8000",
Width: 10,
Strip: "xright",
Padding: "left",
valueAsRsrField: &utils.RSRField{Id: "https://localhost:8000"},
},
&CdreCdrField{
Name: "CombiMed1",
Type: "combimed",
Value: "cost",
Width: 10,
Strip: "xright",
Padding: "left",
Filter: fltrCombiMed,
valueAsRsrField: &utils.RSRField{Id: "cost"},
},
}
eCdreCfg.TrailerFields = []*CdreCdrField{
&CdreCdrField{
Name: "DistributorCode",
Type: "constant",
Value: "VOI",
Width: 3,
valueAsRsrField: &utils.RSRField{Id: "VOI"},
},
&CdreCdrField{
Name: "FileSeqNr",
Type: "metatag",
Value: "export_id",
Width: 5,
Padding: "zeroleft",
valueAsRsrField: &utils.RSRField{Id: "export_id"},
},
}
if rcvCdreCfg := xmlCdreCfgs["CDRE-FW2"].AsCdreConfig(); !reflect.DeepEqual(rcvCdreCfg, eCdreCfg) {
for _, fld := range rcvCdreCfg.ContentFields {
fmt.Printf("Fld: %+v\n", fld)
}
t.Errorf("Expecting: %v, received: %v", eCdreCfg, rcvCdreCfg)
}
}

View File

@@ -1,6 +1,6 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -32,92 +32,140 @@ func ParseCgrXmlConfig(reader io.Reader) (*CgrXmlCfgDocument, error) {
if err := decoder.Decode(xmlConfig); err != nil {
return nil, err
}
if err := xmlConfig.cacheAll(); err != nil {
return nil, err
}
return xmlConfig, nil
}
// Define a format for configuration file, one doc contains more configuration instances, identified by section, type and id
type CgrXmlCfgDocument struct {
XMLName xml.Name `xml:"document"`
Type string `xml:"type,attr"`
Configurations []*CgrXmlConfiguration `xml:"configuration"`
cdrefws map[string]*CgrXmlCdreFwCfg // Cache for processed fixed width config instances, key will be the id of the instance
XMLName xml.Name `xml:"document"`
Type string `xml:"type,attr"`
Configurations []*CgrXmlConfiguration `xml:"configuration"`
cdrcs map[string]*CgrXmlCdrcCfg
cdres map[string]*CgrXmlCdreCfg // Cahe cdrexporter instances, key will be the ID
}
// Storage for raw configuration
type CgrXmlConfiguration struct {
XMLName xml.Name `xml:"configuration"`
Section string `xml:"section,attr"`
Type string `xml:"type,attr"`
Id string `xml:"id,attr"`
RawConfig []byte `xml:",innerxml"` // Used to store the configuration struct, as raw so we can store different types
}
// The CdrExporter Fixed Width configuration instance
type CgrXmlCdreFwCfg struct {
Header *CgrXmlCfgCdrHeader `xml:"header"`
Content *CgrXmlCfgCdrContent `xml:"content"`
Trailer *CgrXmlCfgCdrTrailer `xml:"trailer"`
func (cfgInst *CgrXmlConfiguration) rawConfigElement() []byte {
rawConfig := append([]byte("<element>"), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall into one struct
rawConfig = append(rawConfig, []byte("</element>")...)
return rawConfig
}
// CDR header
type CgrXmlCfgCdrHeader struct {
XMLName xml.Name `xml:"header"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR content
type CgrXmlCfgCdrContent struct {
XMLName xml.Name `xml:"content"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR trailer
type CgrXmlCfgCdrTrailer struct {
XMLName xml.Name `xml:"trailer"`
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
}
// CDR field
type CgrXmlCfgCdrField struct {
XMLName xml.Name `xml:"field"`
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Value string `xml:"value,attr"`
Width int `xml:"width,attr"` // Field width
Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright>
Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right>
Layout string `xml:"layout,attr"` // Eg. time format layout
Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported
}
// Avoid building from raw config string always, so build cache here
func (xmlCfg *CgrXmlCfgDocument) cacheCdreFWCfgs() error {
xmlCfg.cdrefws = make(map[string]*CgrXmlCdreFwCfg)
for _, cfgInst := range xmlCfg.Configurations {
if cfgInst.Section == utils.CDRE || cfgInst.Type == utils.CDRE_FIXED_WIDTH {
cdrefwCfg := new(CgrXmlCdreFwCfg)
rawConfig := append([]byte("<element>"), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall into one struct
rawConfig = append(rawConfig, []byte("</element>")...)
if err := xml.Unmarshal(rawConfig, cdrefwCfg); err != nil {
return err
} else if cdrefwCfg == nil {
return fmt.Errorf("Could not unmarshal CgrXmlCdreFwCfg: %s", cfgInst.Id)
} else { // All good, cache the config instance
xmlCfg.cdrefws[cfgInst.Id] = cdrefwCfg
}
func (xmlCfg *CgrXmlCfgDocument) cacheAll() error {
for _, cacheFunc := range []func() error{xmlCfg.cacheCdrcCfgs, xmlCfg.cacheCdreCfgs} {
if err := cacheFunc(); err != nil {
return err
}
}
return nil
}
func (xmlCfg *CgrXmlCfgDocument) GetCdreFWCfg(instName string) (*CgrXmlCdreFwCfg, error) {
if len(xmlCfg.cdrefws) == 0 { // First time, cache also
if err := xmlCfg.cacheCdreFWCfgs(); err != nil {
return nil, err
// Avoid building from raw config string always, so build cache here
func (xmlCfg *CgrXmlCfgDocument) cacheCdrcCfgs() error {
xmlCfg.cdrcs = make(map[string]*CgrXmlCdrcCfg)
for _, cfgInst := range xmlCfg.Configurations {
if cfgInst.Section != utils.CDRC {
continue // Another type of config instance, not interesting to process
}
cdrcCfg := new(CgrXmlCdrcCfg)
if err := xml.Unmarshal(cfgInst.rawConfigElement(), cdrcCfg); err != nil {
return err
} else if cdrcCfg == nil {
return fmt.Errorf("Could not unmarshal config instance: %s", cfgInst.Id)
}
// Cache rsr fields
for _, fld := range cdrcCfg.CdrFields {
if err := fld.PopulateRSRFields(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Id, err.Error())
}
}
cdrcCfg.setDefaults()
xmlCfg.cdrcs[cfgInst.Id] = cdrcCfg
}
return nil
}
// Avoid building from raw config string always, so build cache here
func (xmlCfg *CgrXmlCfgDocument) cacheCdreCfgs() error {
xmlCfg.cdres = make(map[string]*CgrXmlCdreCfg)
for _, cfgInst := range xmlCfg.Configurations {
if cfgInst.Section != utils.CDRE {
continue
}
cdreCfg := new(CgrXmlCdreCfg)
if err := xml.Unmarshal(cfgInst.rawConfigElement(), cdreCfg); err != nil {
return err
} else if cdreCfg == nil {
return fmt.Errorf("Could not unmarshal CgrXmlCdreCfg: %s", cfgInst.Id)
}
if cdreCfg.Header != nil {
// Cache rsr fields
for _, fld := range cdreCfg.Header.Fields {
if err := fld.populateRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
if err := fld.populateFltrRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
}
}
if cdreCfg.Content != nil {
// Cache rsr fields
for _, fld := range cdreCfg.Content.Fields {
if err := fld.populateRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
if err := fld.populateFltrRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
}
}
if cdreCfg.Trailer != nil {
// Cache rsr fields
for _, fld := range cdreCfg.Trailer.Fields {
if err := fld.populateRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
if err := fld.populateFltrRSRField(); err != nil {
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
}
}
}
xmlCfg.cdres[cfgInst.Id] = cdreCfg
}
return nil
}
// Return instances or filtered instance of cdrefw configuration
func (xmlCfg *CgrXmlCfgDocument) GetCdreCfgs(instName string) map[string]*CgrXmlCdreCfg {
if len(instName) != 0 {
if cfg, hasIt := xmlCfg.cdres[instName]; !hasIt {
return nil
} else {
return map[string]*CgrXmlCdreCfg{instName: cfg}
}
}
if cfg, hasIt := xmlCfg.cdrefws[instName]; hasIt {
return cfg, nil
}
return nil, nil
return xmlCfg.cdres
}
// Return instances or filtered instance of cdrc configuration
func (xmlCfg *CgrXmlCfgDocument) GetCdrcCfgs(instName string) map[string]*CgrXmlCdrcCfg {
if len(instName) != 0 {
if cfg, hasIt := xmlCfg.cdrcs[instName]; !hasIt {
return nil
} else {
return map[string]*CgrXmlCdrcCfg{instName: cfg} // Filtered
}
}
return xmlCfg.cdrcs // Unfiltered
}

View File

@@ -1,119 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package config
import (
"strings"
"testing"
)
var cfgDoc *CgrXmlCfgDocument // Will be populated by first test
func TestParseXmlConfig(t *testing.T) {
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8" ?>
<document type="cgrates/xml">
<configuration section="cdre" type="fixed_width" id="CDRE-FW1">
<header>
<fields>
<field name="TypeOfRecord" type="constant" value="10" width="2"/>
<field name="Filler1" type="filler" width="3"/>
<field name="DistributorCode" type="constant" value="VOI" width="3"/>
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5"/>
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12"/>
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150400" width="12"/>
<field name="Version" type="constant" value="01" width="2"/>
<field name="Filler2" type="filler" width="105"/>
</fields>
</header>
<content>
<fields>
<field name="TypeOfRecord" type="constant" value="20" width="2"/>
<field name="Account" type="cdrfield" value="cgrid" width="12" mandatory="true"/>
<field name="Subject" type="cdrfield" value="subject" strip="left" padding="left" width="5"/>
<field name="CLI" type="cdrfield" value="cli" strip="xright" width="15"/>
<field name="Destination" type="cdrfield" value="destination" strip="xright" width="24"/>
<field name="TOR" type="constant" value="02" width="2"/>
<field name="SubtypeTOR" type="constant" value="11" width="4"/>
<field name="SetupTime" type="cdrfield" value="start_time" layout="020106150400" width="12"/>
<field name="Duration" type="cdrfield" value="duration" width="6"/>
<field name="DataVolume" type="filler" width="6"/>
<field name="TaxCode" type="constant" value="1" width="1"/>
<field name="OperatorCode" type="cdrfield" value="operator" width="2"/>
<field name="ProductId" type="cdrfield" value="productid" width="5"/>
<field name="NetworkId" type="constant" value="3" width="1"/>
<field name="CallId" type="cdrfield" value="accid" width="16"/>
<field name="Filler" type="filler" width="8"/>
<field name="Filler" type="filler" width="8"/>
<field name="TerminationCode" type="concatenated_cdrfield" value="operator,product" width="5"/>
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9"/>
<field name="CalledMask" type="cdrfield" value="calledmask" width="1"/>
</fields>
</content>
<trailer>
<fields>
<field name="TypeOfRecord" type="constant" value="90" width="2"/>
<field name="Filler1" type="filler" width="3"/>
<field name="DistributorCode" type="constant" value="VOI" width="3"/>
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5"/>
<field name="NumberOfRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6"/>
<field name="CdrsDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8"/>
<field name="FirstCdrTime" type="metatag" value="first_cdr_time" layout="020106150400" width="12"/>
<field name="LastCdrTime" type="metatag" value="last_cdr_time" layout="020106150400" width="12"/>
<field name="Filler1" type="filler" width="93"/>
</fields>
</trailer>
</configuration>
</document>`
var err error
reader := strings.NewReader(cfgXmlStr)
if cfgDoc, err = ParseCgrXmlConfig(reader); err != nil {
t.Error(err.Error())
} else if cfgDoc == nil {
t.Fatal("Could not parse xml configuration document")
}
}
func TestCacheCdreFWCfgs(t *testing.T) {
if len(cfgDoc.cdrefws) != 0 {
t.Error("Cache should be empty before caching")
}
if err := cfgDoc.cacheCdreFWCfgs(); err != nil {
t.Error(err)
} else if len(cfgDoc.cdrefws) != 1 {
t.Error("Did not cache")
}
}
func TestGetCdreFWCfg(t *testing.T) {
cdreFWCfg, err := cfgDoc.GetCdreFWCfg("CDRE-FW1")
if err != nil {
t.Error(err)
} else if cdreFWCfg == nil {
t.Error("Could not parse CdreFw instance")
}
if len(cdreFWCfg.Header.Fields) != 8 {
t.Error("Unexpected number of header fields parsed", len(cdreFWCfg.Header.Fields))
}
if len(cdreFWCfg.Content.Fields) != 20 {
t.Error("Unexpected number of content fields parsed", len(cdreFWCfg.Content.Fields))
}
if len(cdreFWCfg.Trailer.Fields) != 9 {
t.Error("Unexpected number of trailer fields parsed", len(cdreFWCfg.Trailer.Fields))
}
}

View File

@@ -19,46 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func init() {
commands["get_account"] = &CmdGetAccount{}
c := &CmdGetAccount{
name: "account",
rpcMethod: "ApierV1.GetAccount",
rpcParams: &utils.AttrGetAccount{Direction: "*out"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetAccount struct {
name string
rpcMethod string
rpcParams *apier.AttrGetAccount
rpcResult *engine.Account
rpcParams *utils.AttrGetAccount
*CommandExecuter
}
// name should be exec's name
func (self *CmdGetAccount) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_account <tenant> <account>")
}
// set param defaults
func (self *CmdGetAccount) defaults() error {
self.rpcMethod = "ApierV1.GetAccount"
self.rpcParams = &apier.AttrGetAccount{BalanceType: engine.CREDIT}
self.rpcParams.Direction = "*out"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdGetAccount) FromArgs(args []string) error {
if len(args) < 4 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.Tenant = args[2]
self.rpcParams.Account = args[3]
return nil
func (self *CmdGetAccount) Name() string {
return self.name
}
func (self *CmdGetAccount) RpcMethod() string {
@@ -66,9 +50,12 @@ func (self *CmdGetAccount) RpcMethod() string {
}
func (self *CmdGetAccount) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.AttrGetAccount{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdGetAccount) RpcResult() interface{} {
return &self.rpcResult
return &engine.Account{}
}

58
console/account_add.go Normal file
View File

@@ -0,0 +1,58 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/apier"
func init() {
c := &CmdAddAccount{
name: "account_add",
rpcMethod: "ApierV1.SetAccount",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdAddAccount struct {
name string
rpcMethod string
rpcParams *apier.AttrSetAccount
*CommandExecuter
}
func (self *CmdAddAccount) Name() string {
return self.name
}
func (self *CmdAddAccount) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdAddAccount) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrSetAccount{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdAddAccount) RpcResult() interface{} {
var s string
return &s
}

View File

@@ -18,42 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
)
import "github.com/cgrates/cgrates/utils"
func init() {
commands["set_accountactions"] = &CmdSetAccountActions{}
c := &CmdSetAccountActions{
name: "accountactions_set",
rpcMethod: "ApierV1.SetAccountActions",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdSetAccountActions struct {
name string
rpcMethod string
rpcParams *utils.TPAccountActions
rpcResult string
*CommandExecuter
}
// name should be exec's name
func (self *CmdSetAccountActions) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] set_accountactions <tpid> <loadid> <tenant> <account>")
}
// set param defaults
func (self *CmdSetAccountActions) defaults() error {
self.rpcMethod = "ApierV1.SetAccountActions"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdSetAccountActions) FromArgs(args []string) error {
if len(args) < 3 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams = &utils.TPAccountActions{TPid: args[2], LoadId: args[3], Tenant: args[4], Account: args[5], Direction: "*out"}
return nil
func (self *CmdSetAccountActions) Name() string {
return self.name
}
func (self *CmdSetAccountActions) RpcMethod() string {
@@ -61,9 +46,13 @@ func (self *CmdSetAccountActions) RpcMethod() string {
}
func (self *CmdSetAccountActions) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.TPAccountActions{}
}
return self.rpcParams
}
func (self *CmdSetAccountActions) RpcResult() interface{} {
return &self.rpcResult
var s string
return &s
}

View File

@@ -18,48 +18,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/apier"
)
import "github.com/cgrates/cgrates/apier"
func init() {
commands["execute_action"] = &CmdExecuteAction{}
c := &CmdExecuteAction{
name: "action_execute",
rpcMethod: "ApierV1.ExecuteAction",
rpcParams: &apier.AttrExecuteAction{Direction: "*out"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdExecuteAction struct {
name string
rpcMethod string
rpcParams *apier.AttrExecuteAction
rpcResult string
*CommandExecuter
}
// name should be exec's name
func (self *CmdExecuteAction) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] execute_action <tenant> <account> <actionsid> [<direction>]")
}
// set param defaults
func (self *CmdExecuteAction) defaults() error {
self.rpcMethod = "ApierV1.ExecuteAction"
self.rpcParams = &apier.AttrExecuteAction{Direction: "*out"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdExecuteAction) FromArgs(args []string) error {
if len(args) < 5 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.Tenant = args[2]
self.rpcParams.Account = args[3]
self.rpcParams.ActionsId = args[4]
if len(args) > 5 {
self.rpcParams.Direction = args[5]
}
return nil
func (self *CmdExecuteAction) Name() string {
return self.name
}
func (self *CmdExecuteAction) RpcMethod() string {
@@ -67,9 +47,13 @@ func (self *CmdExecuteAction) RpcMethod() string {
}
func (self *CmdExecuteAction) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrExecuteAction{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdExecuteAction) RpcResult() interface{} {
return &self.rpcResult
var s string
return &s
}

View File

@@ -1,82 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"strconv"
"github.com/cgrates/cgrates/apier"
)
func init() {
commands["add_account"] = &CmdAddAccount{}
}
// Commander implementation
type CmdAddAccount struct {
rpcMethod string
rpcParams *apier.AttrSetAccount
rpcResult string
}
// name should be exec's name
func (self *CmdAddAccount) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] add_account <tenant> <account> <allownegative> <actiontimingsid> [<direction>]")
}
// set param defaults
func (self *CmdAddAccount) defaults() error {
self.rpcMethod = "ApierV1.SetAccount"
self.rpcParams = &apier.AttrSetAccount{Direction: "*out"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdAddAccount) FromArgs(args []string) error {
if len(args) < 6 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.Tenant = args[2]
self.rpcParams.Account = args[3]
if an, err := strconv.ParseBool(args[4]); err != nil {
return fmt.Errorf("Error parsing allownegative boolean: ", args[4])
} else {
self.rpcParams.AllowNegative = an
}
self.rpcParams.ActionPlanId = args[5]
if len(args) > 6 {
self.rpcParams.Direction = args[6]
}
return nil
}
func (self *CmdAddAccount) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdAddAccount) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdAddAccount) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -1,97 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"strconv"
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/engine"
)
func init() {
commands["add_balance"] = &CmdAddBalance{}
}
// Commander implementation
type CmdAddBalance struct {
rpcMethod string
rpcParams *apier.AttrAddBalance
rpcResult string
}
// name should be exec's name
func (self *CmdAddBalance) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] add_balance <tenant> <account> <value> [*monetary|*sms|*internet|*internet_time|*minutes [<destinationid> [<weight> [overwrite]]]]")
}
// set param defaults
func (self *CmdAddBalance) defaults() error {
self.rpcMethod = "ApierV1.AddBalance"
self.rpcParams = &apier.AttrAddBalance{BalanceType: engine.CREDIT}
self.rpcParams.Direction = "*out"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdAddBalance) FromArgs(args []string) error {
var err error
if len(args) < 5 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.Tenant = args[2]
self.rpcParams.Account = args[3]
value, err := strconv.ParseFloat(args[4], 64)
if err != nil {
return err
}
self.rpcParams.Value = value
if len(args) > 5 {
self.rpcParams.BalanceType = args[5]
}
if len(args) > 6 {
self.rpcParams.DestinationId = args[6]
}
if len(args) > 7 {
if self.rpcParams.Weight, err = strconv.ParseFloat(args[7], 64); err != nil {
return fmt.Errorf("Cannot parse weight parameter")
}
}
if len(args) > 8 {
if args[8] == "overwrite" {
self.rpcParams.Overwrite = true
}
}
return nil
}
func (self *CmdAddBalance) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdAddBalance) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdAddBalance) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -1,90 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"strconv"
"github.com/cgrates/cgrates/apier"
)
func init() {
commands["add_triggeredaction"] = &CmdAddTriggeredAction{}
}
// Commander implementation
type CmdAddTriggeredAction struct {
rpcMethod string
rpcParams *apier.AttrAddActionTrigger
rpcResult string
}
// name should be exec's name
func (self *CmdAddTriggeredAction) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] add_triggeredaction <tenant> <account> <balanceid> <thresholdvalue> <destinationid> <weight> <actionsid> [<direction>]")
}
// set param defaults
func (self *CmdAddTriggeredAction) defaults() error {
self.rpcMethod = "ApierV1.AddTriggeredAction"
self.rpcParams = &apier.AttrAddActionTrigger{Direction: "*out"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdAddTriggeredAction) FromArgs(args []string) error {
if len(args) < 9 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.Tenant = args[2]
self.rpcParams.Account = args[3]
self.rpcParams.BalanceType = args[4]
thresholdvalue, err := strconv.ParseFloat(args[5], 64)
if err != nil {
return err
}
self.rpcParams.ThresholdValue = thresholdvalue
self.rpcParams.DestinationId = args[6]
weight, err := strconv.ParseFloat(args[7], 64)
if err != nil {
return err
}
self.rpcParams.Weight = weight
self.rpcParams.ActionsId = args[8]
if len(args) > 9 {
self.rpcParams.Direction = args[9]
}
return nil
}
func (self *CmdAddTriggeredAction) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdAddTriggeredAction) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdAddTriggeredAction) RpcResult() interface{} {
return &self.rpcResult
}

64
console/balance_debit.go Normal file
View File

@@ -0,0 +1,64 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/engine"
func init() {
c := &CmdDebitBalance{
name: "balance_debit",
rpcMethod: "Responder.Debit",
rpcParams: &engine.CallDescriptor{Direction: "*out"},
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdDebitBalance struct {
name string
rpcMethod string
rpcParams *engine.CallDescriptor
clientArgs []string
*CommandExecuter
}
func (self *CmdDebitBalance) Name() string {
return self.name
}
func (self *CmdDebitBalance) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdDebitBalance) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdDebitBalance) RpcResult() interface{} {
return &engine.CallCost{}
}
func (self *CmdDebitBalance) ClientArgs() []string {
return self.clientArgs
}

61
console/balance_set.go Normal file
View File

@@ -0,0 +1,61 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/engine"
)
func init() {
c := &CmdAddBalance{
name: "balance_set",
rpcMethod: "ApierV1.AddBalance",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdAddBalance struct {
name string
rpcMethod string
rpcParams *apier.AttrAddBalance
*CommandExecuter
}
func (self *CmdAddBalance) Name() string {
return self.name
}
func (self *CmdAddBalance) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdAddBalance) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrAddBalance{BalanceType: engine.CREDIT, Overwrite: false}
}
return self.rpcParams
}
func (self *CmdAddBalance) RpcResult() interface{} {
var s string
return &s
}

View File

@@ -18,40 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
)
import "github.com/cgrates/cgrates/utils"
func init() {
commands["get_cache_age"] = &CmdGetCacheAge{}
c := &CmdGetCacheAge{
name: "cache_age",
rpcMethod: "ApierV1.GetCachedItemAge",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetCacheAge struct {
name string
rpcMethod string
rpcParams string
rpcResult *utils.CachedItemAge
rpcParams *StringWrapper
*CommandExecuter
}
// name should be exec's name
func (self *CmdGetCacheAge) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_cache_age <item_id>")
}
// set param defaults
func (self *CmdGetCacheAge) defaults() error {
self.rpcMethod = "ApierV1.GetCachedItemAge"
return nil
}
func (self *CmdGetCacheAge) FromArgs(args []string) error {
if len(args) != 3 {
return fmt.Errorf(self.Usage(""))
}
self.defaults()
self.rpcParams = args[2]
return nil
func (self *CmdGetCacheAge) Name() string {
return self.name
}
func (self *CmdGetCacheAge) RpcMethod() string {
@@ -59,9 +46,12 @@ func (self *CmdGetCacheAge) RpcMethod() string {
}
func (self *CmdGetCacheAge) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &StringWrapper{}
}
return self.rpcParams
}
func (self *CmdGetCacheAge) RpcResult() interface{} {
return &self.rpcResult
return &utils.CachedItemAge{}
}

View File

@@ -18,37 +18,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
)
import "github.com/cgrates/cgrates/utils"
func init() {
commands["reload_cache"] = &CmdReloadCache{}
c := &CmdReloadCache{
name: "cache_reload",
rpcMethod: "ApierV1.ReloadCache",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdReloadCache struct {
name string
rpcMethod string
rpcParams *utils.ApiReloadCache
rpcResult string
*CommandExecuter
}
// name should be exec's name
func (self *CmdReloadCache) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] reload_cache")
}
// set param defaults
func (self *CmdReloadCache) defaults() error {
self.rpcMethod = "ApierV1.ReloadCache"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdReloadCache) FromArgs(args []string) error {
self.defaults()
return nil
func (self *CmdReloadCache) Name() string {
return self.name
}
func (self *CmdReloadCache) RpcMethod() string {
@@ -56,9 +47,13 @@ func (self *CmdReloadCache) RpcMethod() string {
}
func (self *CmdReloadCache) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.ApiReloadCache{}
}
return self.rpcParams
}
func (self *CmdReloadCache) RpcResult() interface{} {
return &self.rpcResult
var s string
return &s
}

View File

@@ -18,36 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
)
import "github.com/cgrates/cgrates/utils"
func init() {
commands["get_cache_stats"] = &CmdGetCacheStats{}
c := &CmdGetCacheStats{
name: "cache_stats",
rpcMethod: "ApierV1.GetCacheStats",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetCacheStats struct {
name string
rpcMethod string
rpcParams *utils.AttrCacheStats
rpcResult utils.CacheStats
*CommandExecuter
}
// name should be exec's name
func (self *CmdGetCacheStats) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_cache_stats")
}
// set param defaults
func (self *CmdGetCacheStats) defaults() error {
self.rpcMethod = "ApierV1.GetCacheStats"
return nil
}
func (self *CmdGetCacheStats) FromArgs(args []string) error {
self.defaults()
return nil
func (self *CmdGetCacheStats) Name() string {
return self.name
}
func (self *CmdGetCacheStats) RpcMethod() string {
@@ -55,9 +46,12 @@ func (self *CmdGetCacheStats) RpcMethod() string {
}
func (self *CmdGetCacheStats) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.AttrCacheStats{}
}
return self.rpcParams
}
func (self *CmdGetCacheStats) RpcResult() interface{} {
return &self.rpcResult
return &utils.CacheStats{}
}

62
console/callcost.go Normal file
View File

@@ -0,0 +1,62 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func init() {
c := &CmdGetCostDetails{
name: "cost_details",
rpcMethod: "ApierV1.GetCallCostLog",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetCostDetails struct {
name string
rpcMethod string
rpcParams *apier.AttrGetCallCost
rpcResult string
*CommandExecuter
}
func (self *CmdGetCostDetails) Name() string {
return self.name
}
func (self *CmdGetCostDetails) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCostDetails) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrGetCallCost{RunId: utils.DEFAULT_RUNID}
}
return self.rpcParams
}
func (self *CmdGetCostDetails) RpcResult() interface{} {
return &engine.CallCost{}
}

57
console/cdrs_export.go Normal file
View File

@@ -0,0 +1,57 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/utils"
func init() {
c := &CmdExportCdrs{
name: "cdrs_export",
rpcMethod: "ApierV1.ExportCdrsToFile",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdExportCdrs struct {
name string
rpcMethod string
rpcParams *utils.AttrExpFileCdrs
*CommandExecuter
}
func (self *CmdExportCdrs) Name() string {
return self.name
}
func (self *CmdExportCdrs) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdExportCdrs) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.AttrExpFileCdrs{}
}
return self.rpcParams
}
func (self *CmdExportCdrs) RpcResult() interface{} {
return &utils.ExportedFileCdrs{}
}

View File

@@ -18,44 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
)
import "github.com/cgrates/cgrates/utils"
func init() {
commands["rem_cdrs"] = &CmdRemCdrs{}
c := &CmdRemCdrs{
name: "cdrs_rem",
rpcMethod: "ApierV1.RemCdrs",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdRemCdrs struct {
name string
rpcMethod string
rpcParams *utils.AttrRemCdrs
rpcResult string
*CommandExecuter
}
// name should be exec's name
func (self *CmdRemCdrs) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] rem_cdrs <cgrid> [<cdrid> [<cdrid>...]]")
}
// set param defaults
func (self *CmdRemCdrs) defaults() error {
self.rpcMethod = "ApierV1.RemCdrs"
self.rpcParams = &utils.AttrRemCdrs{}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdRemCdrs) FromArgs(args []string) error {
if len(args) < 3 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.CgrIds = args[2:]
return nil
func (self *CmdRemCdrs) Name() string {
return self.name
}
func (self *CmdRemCdrs) RpcMethod() string {
@@ -63,9 +46,13 @@ func (self *CmdRemCdrs) RpcMethod() string {
}
func (self *CmdRemCdrs) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.AttrRemCdrs{}
}
return self.rpcParams
}
func (self *CmdRemCdrs) RpcResult() interface{} {
return &self.rpcResult
var s string
return &s
}

View File

@@ -0,0 +1,57 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/apier"
func init() {
c := &CmdCdrStatsMetrics{
name: "cdrstats_metrics",
rpcMethod: "CDRStatsV1.GetMetrics",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdCdrStatsMetrics struct {
name string
rpcMethod string
rpcParams *apier.AttrGetMetrics
*CommandExecuter
}
func (self *CmdCdrStatsMetrics) Name() string {
return self.name
}
func (self *CmdCdrStatsMetrics) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdCdrStatsMetrics) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrGetMetrics{}
}
return self.rpcParams
}
func (self *CmdCdrStatsMetrics) RpcResult() interface{} {
return &map[string]float64{}
}

View File

@@ -0,0 +1,59 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
func init() {
c := &CmdCdrStatsQueueIds{
name: "cdrstats_queueids",
rpcMethod: "CDRStatsV1.GetQueueIds",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
type CmdCdrStatsQueueIds struct {
name string
rpcMethod string
rpcParams *StringWrapper
*CommandExecuter
}
func (self *CmdCdrStatsQueueIds) Name() string {
return self.name
}
func (self *CmdCdrStatsQueueIds) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdCdrStatsQueueIds) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &StringWrapper{}
}
return self.rpcParams
}
func (self *CmdCdrStatsQueueIds) RpcResult() interface{} {
var s []string
return &s
}
func (self *CmdCdrStatsQueueIds) ClientArgs() (args []string) {
return
}

View File

@@ -0,0 +1,60 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"github.com/cgrates/cgrates/utils"
)
func init() {
c := &CmdCdrReloadQueues{
name: "cdrstats_reload",
rpcMethod: "CDRStatsV1.ReloadQueues",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdCdrReloadQueues struct {
name string
rpcMethod string
rpcParams *utils.AttrCDRStatsReloadQueues
*CommandExecuter
}
func (self *CmdCdrReloadQueues) Name() string {
return self.name
}
func (self *CmdCdrReloadQueues) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdCdrReloadQueues) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.AttrCDRStatsReloadQueues{}
}
return self.rpcParams
}
func (self *CmdCdrReloadQueues) RpcResult() interface{} {
var s string
return &s
}

58
console/cdrstats_reset.go Normal file
View File

@@ -0,0 +1,58 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/utils"
func init() {
c := &CmdCdrResetQueues{
name: "cdrstats_reset",
rpcMethod: "CDRStatsV1.ResetQueues",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdCdrResetQueues struct {
name string
rpcMethod string
rpcParams *utils.AttrCDRStatsReloadQueues
*CommandExecuter
}
func (self *CmdCdrResetQueues) Name() string {
return self.name
}
func (self *CmdCdrResetQueues) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdCdrResetQueues) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.AttrCDRStatsReloadQueues{}
}
return self.rpcParams
}
func (self *CmdCdrResetQueues) RpcResult() interface{} {
var s string
return &s
}

View File

@@ -5,6 +5,7 @@ Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
@@ -19,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"errors"
"fmt"
"strings"
)
@@ -30,29 +30,53 @@ var (
// Console Command interface
type Commander interface {
FromArgs(args []string) error // Load data from os arguments or flag.Args()
Usage(string) string // usage message
RpcMethod() string // Method which should be called remotely
RpcParams() interface{} // Parameters to send out on rpc
RpcResult() interface{} // Only requirement is to have a String method to print on console
defaults() error // set defaults wherever necessary
FromArgs(args string, verbose bool) error // Load data from os arguments or flag.Args()
Usage() string // usage message
RpcMethod() string // Method which should be called remotely
RpcParams() interface{} // Parameters to send out on rpc
RpcResult() interface{} // Only requirement is to have a String method to print on console
ClientArgs() []string // for autocompletion
Name() string
LocalExecute() string
}
func GetCommands() map[string]Commander {
return commands
}
func getAvailabelCommandsErr() error {
var keys []string
for key, _ := range commands {
keys = append(keys, key)
}
return fmt.Errorf("\n\tAvailable commands <%s>\n", strings.Join(keys, "|"))
}
// Process args and return right command Value or error
func GetCommandValue(args []string) (Commander, error) {
if len(args) < 2 {
return nil, errors.New("\n\tUsage: cgr-console [cfg_opts...{-h}] <command>\n")
func GetCommandValue(command string, verbose bool) (Commander, error) {
if len(command) == 0 {
return nil, getAvailabelCommandsErr()
}
cmdVal, exists := commands[args[1]]
firstSpace := strings.Index(command, " ")
var cmdName string
var cmdArgs string
if firstSpace <= 0 {
cmdName = command[:len(command)]
cmdArgs = ""
} else {
cmdName = command[:firstSpace]
cmdArgs = command[firstSpace+1:]
}
cmdVal, exists := commands[cmdName]
if !exists {
var keys []string
for key, _ := range commands {
keys = append(keys, key)
}
return nil, fmt.Errorf("\n\tUsage: cgr-console [cfg_opts...{-h}] <%s>\n", strings.Join(keys, "|"))
return nil, getAvailabelCommandsErr()
}
if err := cmdVal.FromArgs(args); err != nil {
if err := cmdVal.FromArgs(cmdArgs, verbose); err != nil {
return nil, err
}
return cmdVal, nil
}
type StringWrapper struct {
Item string
}

100
console/command_executer.go Normal file
View File

@@ -0,0 +1,100 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
"github.com/cgrates/cgrates/utils"
)
var (
lineR = regexp.MustCompile(`(\w+)\s*=\s*(\[.+?\]|.+?)(?:\s+|$)`)
jsonR = regexp.MustCompile(`"(\w+)":(\[.+?\]|.+?)[,|}]`)
)
// Commander implementation
type CommandExecuter struct {
command Commander
}
func (ce *CommandExecuter) Usage() string {
jsn, _ := json.Marshal(ce.command.RpcParams())
return fmt.Sprintf("\n\tUsage: %s %s \n", ce.command.Name(), FromJSON(jsn, ce.command.ClientArgs()))
}
// Parses command line args and builds CmdBalance value
func (ce *CommandExecuter) FromArgs(args string, verbose bool) error {
if err := json.Unmarshal(ToJSON(args), ce.command.RpcParams()); err != nil {
return err
}
if verbose {
jsn, _ := json.Marshal(ce.command.RpcParams())
fmt.Println(ce.command.Name(), FromJSON(jsn, ce.command.ClientArgs()))
}
return nil
}
func (ce *CommandExecuter) ClientArgs() (args []string) {
val := reflect.ValueOf(ce.command.RpcParams()).Elem()
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
args = append(args, typeField.Name)
}
return
}
// To be overwritten by commands that do not need a rpc call
func (ce *CommandExecuter) LocalExecute() string {
return ""
}
func ToJSON(line string) (jsn []byte) {
if !strings.Contains(line, "=") {
line = fmt.Sprintf("Item=\"%s\"", line)
}
jsn = append(jsn, '{')
for _, group := range lineR.FindAllStringSubmatch(line, -1) {
if len(group) == 3 {
jsn = append(jsn, []byte(fmt.Sprintf("\"%s\":%s,", group[1], group[2]))...)
}
}
jsn = bytes.TrimRight(jsn, ",")
jsn = append(jsn, '}')
return
}
func FromJSON(jsn []byte, interestingFields []string) (line string) {
if !bytes.Contains(jsn, []byte{':'}) {
return fmt.Sprintf("\"%s\"", string(jsn))
}
for _, group := range jsonR.FindAllSubmatch(jsn, -1) {
if len(group) == 3 {
if utils.IsSliceMember(interestingFields, string(group[1])) {
line += fmt.Sprintf("%s=%s ", group[1], group[2])
}
}
}
return strings.TrimSpace(line)
}

View File

@@ -0,0 +1,108 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"encoding/json"
"testing"
)
func TestToJSON(t *testing.T) {
jsn := ToJSON(`TimeStart="Test" Crazy = 1 Mama=true coco Test=1`)
expected := `{"TimeStart":"Test","Crazy":1,"Mama":true,"Test":1}`
if string(jsn) != expected {
t.Errorf("Expected: %s got: %s", expected, jsn)
}
}
func TestToJSONValid(t *testing.T) {
jsn := ToJSON(`TimeStart="Test" Crazy = 1 Mama=true coco Test=1`)
a := make(map[string]interface{})
if err := json.Unmarshal(jsn, &a); err != nil {
t.Error("Error unmarshaling generated json: ", err)
}
}
func TestToJSONEmpty(t *testing.T) {
jsn := ToJSON("")
if string(jsn) != `{"Item":""}` {
t.Error("Error empty: ", string(jsn))
}
}
func TestToJSONString(t *testing.T) {
jsn := ToJSON("1002")
if string(jsn) != `{"Item":"1002"}` {
t.Error("Error string: ", string(jsn))
}
}
func TestToJSONArrayNoSpace(t *testing.T) {
jsn := ToJSON(`Param=["id1","id2","id3"] Another="Patram"`)
if string(jsn) != `{"Param":["id1","id2","id3"],"Another":"Patram"}` {
t.Error("Error string: ", string(jsn))
}
}
func TestToJSONArraySpace(t *testing.T) {
jsn := ToJSON(`Param=["id1", "id2", "id3"] Another="Patram"`)
if string(jsn) != `{"Param":["id1", "id2", "id3"],"Another":"Patram"}` {
t.Error("Error string: ", string(jsn))
}
}
func TestFromJSON(t *testing.T) {
line := FromJSON([]byte(`{"TimeStart":"Test","Crazy":1,"Mama":true,"Test":1}`), []string{"TimeStart", "Crazy", "Mama", "Test"})
expected := `TimeStart="Test" Crazy=1 Mama=true Test=1`
if line != expected {
t.Errorf("Expected: %s got: '%s'", expected, line)
}
}
func TestFromJSONInterestingFields(t *testing.T) {
line := FromJSON([]byte(`{"TimeStart":"Test","Crazy":1,"Mama":true,"Test":1}`), []string{"TimeStart", "Test"})
expected := `TimeStart="Test" Test=1`
if line != expected {
t.Errorf("Expected: %s got: '%s'", expected, line)
}
}
func TestFromJSONString(t *testing.T) {
line := FromJSON([]byte(`1002`), []string{"string"})
expected := `"1002"`
if line != expected {
t.Errorf("Expected: %s got: '%s'", expected, line)
}
}
func TestFromJSONArrayNoSpace(t *testing.T) {
line := FromJSON([]byte(`{"Param":["id1","id2","id3"], "TimeStart":"Test", "Test":1}`), []string{"Param", "TimeStart", "Test"})
expected := `Param=["id1","id2","id3"] TimeStart="Test" Test=1`
if line != expected {
t.Errorf("Expected: %s got: '%s'", expected, line)
}
}
func TestFromJSONArraySpace(t *testing.T) {
line := FromJSON([]byte(`{"Param":["id1", "id2", "id3"], "TimeStart":"Test", "Test":1}`), []string{"Param", "TimeStart", "Test"})
expected := `Param=["id1", "id2", "id3"] TimeStart="Test" Test=1`
if line != expected {
t.Errorf("Expected: %s got: '%s'", expected, line)
}
}

63
console/cost.go Normal file
View File

@@ -0,0 +1,63 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/engine"
func init() {
c := &CmdGetCost{
name: "cost",
rpcMethod: "Responder.GetCost",
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetCost struct {
name string
rpcMethod string
rpcParams *engine.CallDescriptor
clientArgs []string
*CommandExecuter
}
func (self *CmdGetCost) Name() string {
return self.name
}
func (self *CmdGetCost) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCost) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdGetCost) RpcResult() interface{} {
return &engine.CallCost{}
}
func (self *CmdGetCost) ClientArgs() []string {
return self.clientArgs
}

67
console/datacost.go Normal file
View File

@@ -0,0 +1,67 @@
/*
Real-time Charging System for Telecom & ISP environments
Copyright (C) 2012-2014 ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func init() {
c := &CmdGetDataCost{
name: "datacost",
rpcMethod: "ApierV1.GetDataCost",
clientArgs: []string{"Direction", "Category", "Tenant", "Account", "Subject", "StartTime", "Usage"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetDataCost struct {
name string
rpcMethod string
rpcParams *apier.AttrGetDataCost
clientArgs []string
*CommandExecuter
}
func (self *CmdGetDataCost) Name() string {
return self.name
}
func (self *CmdGetDataCost) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetDataCost) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrGetDataCost{Direction: utils.OUT}
}
return self.rpcParams
}
func (self *CmdGetDataCost) RpcResult() interface{} {
return &engine.DataCost{}
}
func (self *CmdGetDataCost) ClientArgs() []string {
return self.clientArgs
}

View File

@@ -1,97 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"time"
)
func init() {
commands["debit_balance"] = &CmdDebitBalance{}
}
// Commander implementation
type CmdDebitBalance struct {
rpcMethod string
rpcParams *engine.CallDescriptor
rpcResult engine.CallCost
}
// name should be exec's name
func (self *CmdDebitBalance) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] debit_balance <tor> <tenant> <subject> <destination> <start_time|*now> <duration>")
}
// set param defaults
func (self *CmdDebitBalance) defaults() error {
self.rpcMethod = "Responder.Debit"
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdDebitBalance) FromArgs(args []string) error {
if len(args) != 8 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
var tStart time.Time
var err error
if args[6] == "*now" {
tStart = time.Now()
} else {
tStart, err = utils.ParseDate(args[6])
if err != nil {
fmt.Println("\n*start_time* should have one of the formats:")
fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC")
fmt.Println("\tunix time\teg: 1383823746")
fmt.Println("\t*now\t\tmetafunction transformed into localtime at query time")
fmt.Println("\t+dur\t\tduration to be added to localtime (valid suffixes: ns, us/µs, ms, s, m, h)\n")
return fmt.Errorf(self.Usage(""))
}
}
callDur, err := utils.ParseDurationWithSecs(args[7])
if err != nil {
fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n")
}
self.rpcParams.TOR = args[2]
self.rpcParams.Tenant = args[3]
self.rpcParams.Subject = args[4]
self.rpcParams.Destination = args[5]
self.rpcParams.TimeStart = tStart
self.rpcParams.CallDuration = callDur
self.rpcParams.TimeEnd = tStart.Add(callDur)
return nil
}
func (self *CmdDebitBalance) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdDebitBalance) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdDebitBalance) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -18,42 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
"github.com/cgrates/cgrates/engine"
)
import "github.com/cgrates/cgrates/engine"
func init() {
commands["get_destination"] = &CmdGetDestination{}
c := &CmdGetDestination{
name: "destination",
rpcMethod: "ApierV1.GetDestination",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetDestination struct {
name string
rpcMethod string
rpcParams string
rpcResult *engine.Destination
rpcParams *StringWrapper
*CommandExecuter
}
// name should be exec's name
func (self *CmdGetDestination) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_destination <id>")
}
// set param defaults
func (self *CmdGetDestination) defaults() error {
self.rpcMethod = "Apier.GetDestination"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdGetDestination) FromArgs(args []string) error {
if len(args) < 3 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams = args[2]
return nil
func (self *CmdGetDestination) Name() string {
return self.name
}
func (self *CmdGetDestination) RpcMethod() string {
@@ -61,10 +46,12 @@ func (self *CmdGetDestination) RpcMethod() string {
}
func (self *CmdGetDestination) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &StringWrapper{}
}
return self.rpcParams
}
func (self *CmdGetDestination) RpcResult() interface{} {
self.rpcResult = new(engine.Destination)
return self.rpcResult
return &engine.Destination{}
}

View File

@@ -1,92 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
"time"
)
func init() {
commands["export_cdrs"] = &CmdExportCdrs{}
}
// Commander implementation
type CmdExportCdrs struct {
rpcMethod string
rpcParams *utils.AttrExpFileCdrs
rpcResult utils.ExportedFileCdrs
}
// name should be exec's name
func (self *CmdExportCdrs) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] export_cdrs <dry_run|csv> [<start_time|*one_month> [<stop_time> ]]")
}
// set param defaults
func (self *CmdExportCdrs) defaults() error {
self.rpcMethod = "ApierV1.ExportCdrsToFile"
self.rpcParams = &utils.AttrExpFileCdrs{CdrFormat: "csv"}
return nil
}
func (self *CmdExportCdrs) FromArgs(args []string) error {
self.defaults()
var timeStart, timeEnd string
if len(args) < 3 {
return fmt.Errorf(self.Usage(""))
}
if !utils.IsSliceMember(utils.CdreCdrFormats, args[2]) {
return fmt.Errorf(self.Usage(""))
}
self.rpcParams.CdrFormat = args[2]
switch len(args) {
case 4:
timeStart = args[3]
case 5:
timeStart = args[3]
timeEnd = args[4]
case 6:
timeStart = args[3]
timeEnd = args[4]
}
if timeStart == "*one_month" {
now := time.Now()
self.rpcParams.TimeStart = now.AddDate(0, -1, 0).String()
self.rpcParams.TimeEnd = now.String()
} else {
self.rpcParams.TimeStart = timeStart
self.rpcParams.TimeEnd = timeEnd
}
return nil
}
func (self *CmdExportCdrs) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdExportCdrs) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdExportCdrs) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -1,76 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/apier"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
func init() {
commands["get_callcost"] = &CmdGetCallCost{}
}
// Commander implementation
type CmdGetCallCost struct {
rpcMethod string
rpcParams *apier.AttrGetCallCost
rpcResult *engine.CallCost
}
// name should be exec's name
func (self *CmdGetCallCost) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_callcost <cgrid> [<runid>]")
}
// set param defaults
func (self *CmdGetCallCost) defaults() error {
self.rpcMethod = "ApierV1.GetCallCostLog"
self.rpcParams = &apier.AttrGetCallCost{RunId: utils.DEFAULT_RUNID}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdGetCallCost) FromArgs(args []string) error {
if len(args) < 3 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams.CgrId = args[2]
if len(args) == 4 {
self.rpcParams.RunId = args[3]
}
return nil
}
func (self *CmdGetCallCost) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCallCost) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetCallCost) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -1,97 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"time"
)
func init() {
commands["get_cost"] = &CmdGetCost{}
}
// Commander implementation
type CmdGetCost struct {
rpcMethod string
rpcParams *engine.CallDescriptor
rpcResult engine.CallCost
}
// name should be exec's name
func (self *CmdGetCost) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_cost <tor> <tenant> <subject> <destination> <start_time|*now> <duration>")
}
// set param defaults
func (self *CmdGetCost) defaults() error {
self.rpcMethod = "Responder.GetCost"
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdGetCost) FromArgs(args []string) error {
if len(args) != 8 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
var tStart time.Time
var err error
if args[6] == "*now" {
tStart = time.Now()
} else {
tStart, err = utils.ParseDate(args[6])
if err != nil {
fmt.Println("\n*start_time* should have one of the formats:")
fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC")
fmt.Println("\tunix time\teg: 1383823746")
fmt.Println("\t*now\t\tmetafunction transformed into localtime at query time")
fmt.Println("\t+dur\t\tduration to be added to localtime (valid suffixes: ns, us/µs, ms, s, m, h)\n")
return fmt.Errorf(self.Usage(""))
}
}
callDur, err := utils.ParseDurationWithSecs(args[7])
if err != nil {
fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n")
}
self.rpcParams.TOR = args[2]
self.rpcParams.Tenant = args[3]
self.rpcParams.Subject = args[4]
self.rpcParams.Destination = args[5]
self.rpcParams.TimeStart = tStart
self.rpcParams.CallDuration = callDur
self.rpcParams.TimeEnd = tStart.Add(callDur)
return nil
}
func (self *CmdGetCost) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCost) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetCost) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -1,102 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"time"
)
func init() {
commands["get_maxduration"] = &CmdGetMaxDuration{}
}
// Commander implementation
type CmdGetMaxDuration struct {
rpcMethod string
rpcParams *engine.CallDescriptor
rpcResult *float64
}
// name should be exec's name
func (self *CmdGetMaxDuration) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_maxduration <tor> <tenant> <subject> <destination> <start_time|*now> [<target_duration>]")
}
// set param defaults
func (self *CmdGetMaxDuration) defaults() error {
self.rpcMethod = "Responder.GetMaxSessionTime"
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdGetMaxDuration) FromArgs(args []string) error {
if len(args) < 7 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
var tStart time.Time
var err error
if args[6] == "*now" {
tStart = time.Now()
} else {
tStart, err = utils.ParseDate(args[6])
if err != nil {
fmt.Println("\n*start_time* should have one of the formats:")
fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC")
fmt.Println("\tunix time\teg: 1383823746")
fmt.Println("\t*now\t\tmetafunction transformed into localtime at query time")
fmt.Println("\t+dur\t\tduration to be added to localtime (valid suffixes: ns, us/µs, ms, s, m, h)\n")
return fmt.Errorf(self.Usage(""))
}
}
var callDur time.Duration
if len(args) == 8 {
callDur, err = utils.ParseDurationWithSecs(args[7])
if err != nil {
fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n")
}
} else { // Enforce call duration to a predefined 7200s
callDur = time.Duration(7200) * time.Second
}
self.rpcParams.TOR = args[2]
self.rpcParams.Tenant = args[3]
self.rpcParams.Subject = args[4]
self.rpcParams.Destination = args[5]
self.rpcParams.TimeStart = tStart
self.rpcParams.CallDuration = callDur
self.rpcParams.TimeEnd = tStart.Add(callDur)
return nil
}
func (self *CmdGetMaxDuration) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetMaxDuration) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetMaxDuration) RpcResult() interface{} {
return &self.rpcResult
}

64
console/maxduration.go Normal file
View File

@@ -0,0 +1,64 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/engine"
func init() {
c := &CmdGetMaxDuration{
name: "maxduration",
rpcMethod: "Responder.GetMaxSessionTime",
clientArgs: []string{"Direction", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdGetMaxDuration struct {
name string
rpcMethod string
rpcParams *engine.CallDescriptor
clientArgs []string
*CommandExecuter
}
func (self *CmdGetMaxDuration) Name() string {
return self.name
}
func (self *CmdGetMaxDuration) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetMaxDuration) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdGetMaxDuration) RpcResult() interface{} {
var f float64
return &f
}
func (self *CmdGetMaxDuration) ClientArgs() []string {
return self.clientArgs
}

75
console/parse.go Normal file
View File

@@ -0,0 +1,75 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/utils"
func init() {
c := &CmdParse{
name: "parse",
rpcParams: &AttrParse{},
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
type AttrParse struct {
Expression string
Value string
}
type CmdParse struct {
name string
rpcMethod string
rpcParams *AttrParse
*CommandExecuter
}
func (self *CmdParse) Name() string {
return self.name
}
func (self *CmdParse) RpcMethod() string {
return ""
}
func (self *CmdParse) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &AttrParse{}
}
return self.rpcParams
}
func (self *CmdParse) RpcResult() interface{} {
return nil
}
func (self *CmdParse) LocalExecute() string {
if self.rpcParams.Expression == "" {
return "Empty expression error"
}
if self.rpcParams.Value == "" {
return "Empty value error"
}
if rsrField, err := utils.NewRSRField(self.rpcParams.Expression); err == nil {
return rsrField.ParseValue(self.rpcParams.Value)
} else {
return err.Error()
}
}

View File

@@ -0,0 +1,59 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/utils"
func init() {
c := &CmdSetRatingProfile{
name: "ratingprofile_set",
rpcMethod: "ApierV1.SetRatingProfile",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdSetRatingProfile struct {
name string
rpcMethod string
rpcParams *utils.TPRatingProfile
rpcResult string
*CommandExecuter
}
func (self *CmdSetRatingProfile) Name() string {
return self.name
}
func (self *CmdSetRatingProfile) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdSetRatingProfile) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &utils.TPRatingProfile{}
}
return self.rpcParams
}
func (self *CmdSetRatingProfile) RpcResult() interface{} {
var s string
return &s
}

View File

@@ -18,36 +18,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
)
func init() {
commands["reload_scheduler"] = &CmdReloadScheduler{}
c := &CmdReloadScheduler{
name: "scheduler_reload",
rpcMethod: "ApierV1.ReloadScheduler",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdReloadScheduler struct {
name string
rpcMethod string
rpcParams string
rpcResult string
rpcParams *StringWrapper
*CommandExecuter
}
// name should be exec's name
func (self *CmdReloadScheduler) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] reload_scheduler")
}
// set param defaults
func (self *CmdReloadScheduler) defaults() error {
self.rpcMethod = "ApierV1.ReloadScheduler"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdReloadScheduler) FromArgs(args []string) error {
self.defaults()
return nil
func (self *CmdReloadScheduler) Name() string {
return self.name
}
func (self *CmdReloadScheduler) RpcMethod() string {
@@ -55,9 +44,13 @@ func (self *CmdReloadScheduler) RpcMethod() string {
}
func (self *CmdReloadScheduler) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &StringWrapper{}
}
return self.rpcParams
}
func (self *CmdReloadScheduler) RpcResult() interface{} {
return &self.rpcResult
var s string
return &s
}

View File

@@ -1,69 +0,0 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/utils"
)
func init() {
commands["set_ratingprofile"] = &CmdSetrRatingProfile{}
}
// Commander implementation
type CmdSetrRatingProfile struct {
rpcMethod string
rpcParams *utils.TPRatingProfile
rpcResult string
}
// name should be exec's name
func (self *CmdSetrRatingProfile) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] set_ratingprofile <tpid> <loadid> <tenant> <tor> <subject>")
}
// set param defaults
func (self *CmdSetrRatingProfile) defaults() error {
self.rpcMethod = "ApierV1.SetRatingProfile"
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdSetrRatingProfile) FromArgs(args []string) error {
if len(args) < 3 {
return fmt.Errorf(self.Usage(""))
}
// Args look OK, set defaults before going further
self.defaults()
self.rpcParams = &utils.TPRatingProfile{TPid: args[2], LoadId: args[3], Tenant: args[4], TOR: args[5], Direction: "*out", Subject: args[6]}
return nil
}
func (self *CmdSetrRatingProfile) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdSetrRatingProfile) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdSetrRatingProfile) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -18,32 +18,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package console
import (
"fmt"
)
func init() {
commands["status"] = &CmdStatus{}
c := &CmdStatus{
name: "status",
rpcMethod: "Responder.Status",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
type CmdStatus struct {
name string
rpcMethod string
rpcParams string
rpcResult string
rpcParams *StringWrapper
*CommandExecuter
}
func (self *CmdStatus) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: %s [cfg_opts...{-h}] status", name)
}
func (self *CmdStatus) defaults() error {
self.rpcMethod = "Responder.Status"
return nil
}
func (self *CmdStatus) FromArgs(args []string) error {
self.defaults()
return nil
func (self *CmdStatus) Name() string {
return self.name
}
func (self *CmdStatus) RpcMethod() string {
@@ -51,9 +43,17 @@ func (self *CmdStatus) RpcMethod() string {
}
func (self *CmdStatus) RpcParams() interface{} {
return &self.rpcParams
if self.rpcParams == nil {
self.rpcParams = &StringWrapper{}
}
return self.rpcParams
}
func (self *CmdStatus) RpcResult() interface{} {
return &self.rpcResult
var s string
return &s
}
func (self *CmdStatus) ClientArgs() (args []string) {
return
}

View File

@@ -0,0 +1,58 @@
/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package console
import "github.com/cgrates/cgrates/apier"
func init() {
c := &CmdAddTriggeredAction{
name: "triggeredaction_add",
rpcMethod: "ApierV1.AddTriggeredAction",
}
commands[c.Name()] = c
c.CommandExecuter = &CommandExecuter{c}
}
// Commander implementation
type CmdAddTriggeredAction struct {
name string
rpcMethod string
rpcParams *apier.AttrAddActionTrigger
*CommandExecuter
}
func (self *CmdAddTriggeredAction) Name() string {
return self.name
}
func (self *CmdAddTriggeredAction) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdAddTriggeredAction) RpcParams() interface{} {
if self.rpcParams == nil {
self.rpcParams = &apier.AttrAddActionTrigger{Direction: "*out"}
}
return self.rpcParams
}
func (self *CmdAddTriggeredAction) RpcResult() interface{} {
var s string
return &s
}

View File

@@ -1,39 +1,39 @@
# CGRateS Configuration file
# Real-time Charging System for Telecom & ISP environments
# Copyright (C) ITsysCOM GmbH
#
# This file contains the default configuration hardcoded into CGRateS.
# This is what you get when you load CGRateS with an empty configuration file.
# [global] must exist in all files, rest of the configuration is inter-changeable.
[global]
# ratingdb_type = redis # Rating subsystem database: <redis>.
# ratingdb_host = 127.0.0.1 # Rating subsystem database host address.
# ratingdb_host = 127.0.0.1 # Rating subsystem database host address.
# ratingdb_port = 6379 # Rating subsystem port to reach the database.
# ratingdb_name = 10 # Rating subsystem database name to connect to.
# ratingdb_user = # Rating subsystem username to use when connecting to database.
# ratingdb_passwd = # Rating subsystem password to use when connecting to database.
# ratingdb_user = # Rating subsystem username to use when connecting to database.
# ratingdb_passwd = # Rating subsystem password to use when connecting to database.
# accountdb_type = redis # Accounting subsystem database: <redis>.
# accountdb_host = 127.0.0.1 # Accounting subsystem database host address.
# accountdb_host = 127.0.0.1 # Accounting subsystem database host address.
# accountdb_port = 6379 # Accounting subsystem port to reach the database.
# accountdb_name = 11 # Accounting subsystem database name to connect to.
# accountdb_user = # Accounting subsystem username to use when connecting to database.
# accountdb_user = # Accounting subsystem username to use when connecting to database.
# accountdb_passwd = # Accounting subsystem password to use when connecting to database.
# stordb_type = mysql # Stor database type to use: <mysql>
# stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets.
# stordb_port = 3306 # The port to reach the logdb.
# stordb_name = cgrates # The name of the log database to connect to.
# stordb_user = cgrates # Username to use when connecting to stordb.
# stordb_passwd = CGRateS.org # Password to use when connecting to stordb.
# stordb_passwd = CGRateS.org # Password to use when connecting to stordb.
# dbdata_encoding = msgpack # The encoding used to store object data in strings: <msgpack|json>
# rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address
# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address
# http_listen = 127.0.0.1:2080 # HTTP listening address
# rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address
# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address
# http_listen = 127.0.0.1:2080 # HTTP listening address
# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
# default_tor = call # Default Type of Record to consider when missing from requests.
# default_tenant = cgrates.org # Default Tenant to consider when missing from requests.
# default_category = call # Default Type of Record to consider when missing from requests.
# default_tenant = cgrates.org # Default Tenant to consider when missing from requests.
# default_subject = cgrates # Default rating Subject to consider when missing from requests.
# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down>
# rounding_decimals = 4 # Number of decimals to round float/costs at
# xmlcfg_path = # Path towards additional config defined in xml file
# rounding_decimals = 10 # System level precision for floats
# http_skip_tls_veify = false # If enabled Http Client will accept any TLS certificate
# xmlcfg_path = # Path towards additional config defined in xml file
[balancer]
# enabled = false # Start Balancer service: <true|false>.
@@ -49,87 +49,126 @@
# enabled = false # Start the CDR Server service: <true|false>.
# extra_fields = # Extra fields to store in CDRs for non-generic CDRs
# mediator = # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
# cdrstats = # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
# store_disable = false # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
[cdre]
# cdr_format = csv # Exported CDRs format <csv>
# export_dir = /var/log/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
# export_template = cgrid,mediation_runid,accid,cdrhost,reqtype,direction,tenant,tor,account,subject,destination,setup_time,answer_time,duration,cost
# Exported fields template <""|fld1,fld2|*xml:instance_name>
# cdr_format = csv # Exported CDRs format <csv>
# data_usage_multiply_factor = 0.0 # Multiply data usage before export (eg: convert from KBytes to Bytes)
# cost_multiply_factor = 0.0 # Multiply cost before export (0.0 to disable), eg: add VAT
# cost_rounding_decimals = -1 # Rounding decimals for Cost values. -1 to disable rounding
# cost_shift_digits = 0 # Shift digits in the cost on export (eg: convert from EUR to cents)
# mask_destination_id = # Destination id containing called addresses to be masked on export
# mask_length = 0 # Length of the destination suffix to be masked
# export_dir = /var/log/cgrates/cdre # Path where the exported CDRs will be placed
# export_template = cgrid,mediation_runid,tor,accid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,usage,cost
# Exported fields template <""|fld1,fld2|*xml:instance_name>
[cdrc]
# enabled = false # Enable CDR client functionality
# cdrs = internal # Address where to reach CDR server. <internal|127.0.0.1:2080>
# cdrs_method = http_cgr # Mechanism to use when posting CDRs on server <http_cgr>
# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify
# cdr_type = csv # CDR file format <csv|freeswitch_csv>.
# cdr_in_dir = /var/log/cgrates/cdr/cdrc/in # Absolute path towards the directory where the CDRs are stored.
# cdr_out_dir = /var/log/cgrates/cdr/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
# cdr_source_id = freeswitch_csv # Free form field, tag identifying the source of the CDRs within CGRS database.
# accid_field = 0 # Accounting id field identifier. Use index number in case of .csv cdrs.
# reqtype_field = 1 # Request type field identifier. Use index number in case of .csv cdrs.
# direction_field = 2 # Direction field identifier. Use index numbers in case of .csv cdrs.
# tenant_field = 3 # Tenant field identifier. Use index numbers in case of .csv cdrs.
# tor_field = 4 # Type of Record field identifier. Use index numbers in case of .csv cdrs.
# account_field = 5 # Account field identifier. Use index numbers in case of .csv cdrs.
# subject_field = 6 # Subject field identifier. Use index numbers in case of .csv CDRs.
# destination_field = 7 # Destination field identifier. Use index numbers in case of .csv cdrs.
# setup_time_field = 8 # Setup time field identifier. Use index numbers in case of .csv cdrs.
# answer_time_field = 9 # Answer time field identifier. Use index numbers in case of .csv cdrs.
# duration_field = 10 # Duration field identifier. Use index numbers in case of .csv cdrs.
# extra_fields = # Extra fields identifiers. For .csv, format: <label_extrafield_1>:<index_extrafield_1>[...,<label_extrafield_n>:<index_extrafield_n>]
# enabled = false # Enable CDR client functionality
# cdrs = internal # Address where to reach CDR server. <internal|127.0.0.1:2080>
# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify
# cdr_type = csv # CDR file format <csv|freeswitch_csv>.
# csv_separator = , # Separator used in case of csv files. One character only supported and needs to be right after equal sign
# cdr_in_dir = /var/log/cgrates/cdrc/in # Absolute path towards the directory where the CDRs are stored.
# cdr_out_dir = /var/log/cgrates/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
# cdr_source_id = csv # Free form field, tag identifying the source of the CDRs within CGRS database.
# tor_field = 2 # TypeOfRecord field identifier. Use index number in case of .csv cdrs.
# accid_field = 3 # Accounting id field identifier. Use index number in case of .csv cdrs.
# reqtype_field = 4 # Request type field identifier. Use index number in case of .csv cdrs.
# direction_field = 5 # Direction field identifier. Use index numbers in case of .csv cdrs.
# tenant_field = 6 # Tenant field identifier. Use index numbers in case of .csv cdrs.
# category_field = 7 # Type of Record field identifier. Use index numbers in case of .csv cdrs.
# account_field = 8 # Account field identifier. Use index numbers in case of .csv cdrs.
# subject_field = 9 # Subject field identifier. Use index numbers in case of .csv CDRs.
# destination_field = 10 # Destination field identifier. Use index numbers in case of .csv cdrs.
# setup_time_field = 11 # Setup time field identifier. Use index numbers in case of .csv cdrs.
# answer_time_field = 12 # Answer time field identifier. Use index numbers in case of .csv cdrs.
# usage_field = 13 # Usage field identifier. Use index numbers in case of .csv cdrs.
# extra_fields = # Extra fields identifiers. For .csv, format: <label_extrafield_1>:<index_extrafield_1>[...,<label_extrafield_n>:<index_extrafield_n>]
[mediator]
# enabled = false # Starts Mediator service: <true|false>.
# reconnects = 3 # Number of reconnects to rater/cdrs before giving up.
# rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
# run_ids = # Identifiers of each extra mediation to run on CDRs
# reqtype_fields = # Name of request type fields to be used during extra mediation. Use index number in case of .csv cdrs.
# direction_fields = # Name of direction fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# tenant_fields = # Name of tenant fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# tor_fields = # Name of tor fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# account_fields = # Name of account fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# subject_fields = # Name of fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# destination_fields = # Name of destination fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# setup_time_fields = # Name of setup_time fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# answer_time_fields = # Name of answer_time fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# duration_fields = # Name of duration fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
# cdrstats = internal # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
# store_disable = false # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
[cdrstats]
# enabled = false # Starts the cdrstats service: <true|false>
# queue_length = 50 # Number of items in the stats buffer
# time_window = 1h # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
# metrics = ASR, ACD, ACC # Stat metric ids to build
# setup_interval = # Filter on CDR SetupTime
# tors = # Filter on CDR TOR fields
# cdr_hosts= # Filter on CDR CdrHost fields
# cdr_sources = # Filter on CDR CdrSource fields
# req_types = # Filter on CDR ReqType fields
# directions = # Filter on CDR Direction fields
# tenants = # Filter on CDR Tenant fields
# categories = # Filter on CDR Category fields
# accounts = # Filter on CDR Account fields
# subjects = # Filter on CDR Subject fields
# destination_prefixes = # Filter on CDR Destination prefixes
# usage_interval = # Filter on CDR Usage
# mediation_run_ids = # Filter on CDR MediationRunId fields
# rated_accounts = # Filter on CDR RatedAccount fields
# rated_subjects = # Filter on CDR RatedSubject fields
# cost_intervals = # Filter on CDR Cost
[session_manager]
# enabled = false # Starts SessionManager service: <true|false>.
# switch_type = freeswitch # Defines the type of switch behind: <freeswitch>.
# rater = internal # Address where to reach the Rater.
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
# debit_interval = 10 # Interval to perform debits on.
# max_call_duration = 3h # Maximum call duration a prepaid call can last
# run_ids = # Identifiers of additional sessions control.
# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
# tor_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
# account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>.
# subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>.
# destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
# enabled = false # Starts SessionManager service: <true|false>
# switch_type = freeswitch # Defines the type of switch behind: <freeswitch>
# rater = internal # Address where to reach the Rater <""|internal|127.0.0.1:2013>
# cdrs = # Address where to reach CDR Server, empty to disable CDR capturing <""|internal|127.0.0.1:2013>
# reconnects = 3 # Number of reconnects to rater/cdrs before giving up.
# debit_interval = 10 # Interval to perform debits on.
# min_call_duration = 0s # Only authorize calls with allowed duration bigger than this
# max_call_duration = 3h # Maximum call duration a prepaid call can last
[freeswitch]
# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
# passwd = ClueCon # FreeSWITCH socket password.
# reconnects = 5 # Number of attempts on connect failure.
# min_dur_low_balance = 5s # Threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval)
# low_balance_ann_file = # File to be played when low balance is reached for prepaid calls
# empty_balance_context = # If defined, prepaid calls will be transfered to this context on empty balance
# empty_balance_ann_file = # File to be played before disconnecting prepaid calls on empty balance (applies only if no context defined)
# cdr_extra_fields = # Extra fields to store in CDRs in case of processing them
[opensips]
# listen_udp = 127.0.0.1:2020 # Address where to listen for datagram events coming from OpenSIPS
# mi_addr = 127.0.0.1:8020 # Adress where to reach OpenSIPS mi_datagram module
# events_subscribe_interval = 60s # Automatic events subscription to OpenSIPS, 0 to disable it
# reconnects = 3 # Number of attempts on connect failure.
[derived_charging]
# run_ids = # Identifiers of additional sessions control.
# run_filters = # List of cdr field filters for each run.
# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
# category_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
# account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>.
# subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>.
# destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
# usage_fields = # Name of usage fields to be used during additional sessions control <""|*default|field_name>.
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
[history_server]
# enabled = false # Starts History service: <true|false>.
# enabled = false # Starts History service: <true|false>.
# history_dir = /var/log/cgrates/history # Location on disk where to store history files.
# save_interval = 1s # Interval to save changed cache into .git archive
# save_interval = 1s # Interval to save changed cache into .git archive
[history_agent]
# enabled = false # Starts History as a client: <true|false>.
# server = internal # Address where to reach the master history server: <internal|x.y.z.y:1234>
[mailer]
# server = localhost # The server to use when sending emails out
# auth_user = cgrates # Authenticate to email server using this user
# auth_passwd = CGRateS.org # Authenticate to email server with this password
# server = localhost # The server to use when sending emails out
# auth_user = cgrates # Authenticate to email server using this user
# auth_passwd = CGRateS.org # Authenticate to email server with this password
# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out

View File

@@ -18,18 +18,15 @@ mediator = internal # Address where to reach the Mediator. Empty for disabli
export_dir = /tmp/cgrates/cdr/cdre/csv # Path where the exported CDRs will be placed
[cdrc]
enabled = true
cdrs = 127.0.0.1:2080
cdr_in_dir = /tmp/cgrates/cdr/cdrc/in # Absolute path towards the directory where the CDRs are stored.
cdr_out_dir =/tmp/cgrates/cdr/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
[mediator]
enabled = true # Starts Mediator service: <true|false>.
rater = 127.0.0.1:2012 # Address where to reach the Rater: <internal|x.y.z.y:1234>
[history_server]
enabled = true # Starts History service: <true|false>.
history_dir = /tmp/cgrates/history # Location on disk where to store history files.
[history_agent]
enabled = true # Starts History as a client: <true|false>.
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
cdrstats=

View File

@@ -0,0 +1,22 @@
# Real-time Charging System for Telecom & ISP environments
# Copyright (C) ITsysCOM GmbH
#
# This file contains the default configuration hardcoded into CGRateS.
# This is what you get when you load CGRateS with an empty configuration file.
[rater]
enabled = true
[cdrs]
enabled = true
mediator = internal
store_disable = true
[mediator]
enabled = true
store_disable = true
[cdrstats]
enabled = true
queue_length = 5 # Maximum number of items in the stats buffer
time_window = 0 # Queue is not affected by the SetupTime

View File

@@ -1,20 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdre" type="fixed_width" id="CDREFW-A">
<header>
<fields>
<field name="Filler1" type="filler" width="4"/>
</fields>
</header>
<content>
<fields>
<field name="TypeOfRecord" type="constant" value="call"/>
</fields>
</content>
<trailer>
<fields>
<field name="Filler1" type="filler" width="3"/>
</fields>
</trailer>
<cdr_format>fwv</cdr_format>
<data_usage_multiply_factor>0.0</data_usage_multiply_factor>
<cost_multiply_factor>0.0</cost_multiply_factor>
<cost_shift_digits>0</cost_shift_digits>
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
<mask_length>0</mask_length>
<export_dir>/var/log/cgrates/cdre</export_dir>
<export_template>
<header>
<fields>
<field name="Filler1" type="filler" width="4" />
</fields>
</header>
<content>
<fields>
<field name="TypeOfRecord" type="constant" value="call" />
</fields>
</content>
<trailer>
<fields>
<field name="Filler1" type="filler" width="3" />
</fields>
</trailer>
</export_template>
</configuration>
</document>

View File

@@ -0,0 +1,32 @@
# CGRateS Configuration file
#
# Used in mediator_local_test
# Starts rater, cdrs and mediator connecting over internal channel
[rater]
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
[cdrs]
enabled = true # Start the CDR Server service: <true|false>.
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
[cdre]
export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
[mediator]
enabled = true # Starts Mediator service: <true|false>.
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
[derived_charging]
run_ids = run2 # Identifiers of additional sessions control.
reqtype_fields = *default # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
direction_fields = *default # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
tenant_fields = *default # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
tor_fields = *default # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
account_fields = ^dc2 # Name of account fields to be used during additional sessions control <""|*default|field_name>.
subject_fields = ^dc2 # Name of fields to be used during additional sessions control <""|*default|field_name>.
destination_fields = *default # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.

View File

@@ -6,6 +6,9 @@
[rater]
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
[scheduler]
enabled = true # Starts Scheduler service: <true|false>.
[cdrs]
enabled = true # Start the CDR Server service: <true|false>.
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
@@ -16,5 +19,20 @@ export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will
[mediator]
enabled = true # Starts Mediator service: <true|false>.
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
cdrstats =
[derived_charging]
run_ids = run2 # Identifiers of additional sessions control.
reqtype_fields = *default # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
direction_fields = *default # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
tenant_fields = *default # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
category_fields = *default # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
account_fields = ^dc2 # Name of account fields to be used during additional sessions control <""|*default|field_name>.
subject_fields = ^dc2 # Name of fields to be used during additional sessions control <""|*default|field_name>.
destination_fields = *default # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
setup_time_fields = *default # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
answer_time_fields = *default # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
usage_fields = *default # Name of usage fields to be used during additional sessions control <""|*default|field_name>.

View File

@@ -0,0 +1,28 @@
# Real-time Charging System for Telecom & ISP environments
# Copyright (C) 2012-2014 ITsysCOM GmbH
[global]
xmlcfg_path = /usr/share/cgrates/conf/samples/multiplecdrc_fwexport.xml
[rater]
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
[scheduler]
enabled = true # Starts Scheduler service: <true|false>.
[cdrs]
enabled = true # Start the CDR Server service: <true|false>.
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
[cdre]
export_dir = /tmp/cgrates/cdr/cdre/csv # Path where the exported CDRs will be placed
export_template = *xml:CDRE-FW1
[cdrc]
enabled = true
cdr_in_dir = /tmp/cgrates/cdrc1/in # Absolute path towards the directory where the CDRs are stored.
cdr_out_dir =/tmp/cgrates/cdrc1/out # Absolute path towards the directory where processed CDRs will be moved.
cdr_source_id = csv1 # Free form field, tag identifying the source of the CDRs within CGRS database.
[mediator]
enabled = true # Starts Mediator service: <true|false>.

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="cgrates/xml">
<configuration section="cdrc" type="csv" id="CDRC-CSV2">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<csv_separator>,</csv_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/tmp/cgrates/cdrc2/in</cdr_in_dir>
<cdr_out_dir>/tmp/cgrates/cdrc2/out</cdr_out_dir>
<cdr_source_id>csv2</cdr_source_id>
<fields>
<field id="tor" filter="~7:s/^(voice|data|sms)$/*$1/" />
<field id="accid" filter="0" />
<field id="reqtype" filter="^rated" />
<field id="direction" filter="^*out" />
<field id="tenant" filter="^cgrates.org" />
<field id="category" filter="~7:s/^voice$/call/" />
<field id="account" filter="3" />
<field id="subject" filter="3" />
<field id="destination" filter="~5:s/^0([1-9]\d+)$/+49$1/" />
<field id="setup_time" filter="1" />
<field id="answer_time" filter="1" />
<field id="usage" filter="~9:s/^(\d+)$/${1}s/" />
</fields>
</configuration>
<configuration section="cdrc" type="csv" id="CDRC-CSV3">
<enabled>true</enabled>
<cdrs_address>internal</cdrs_address>
<cdr_type>csv</cdr_type>
<csv_separator>;</csv_separator>
<run_delay>0</run_delay>
<cdr_in_dir>/tmp/cgrates/cdrc3/in</cdr_in_dir>
<cdr_out_dir>/tmp/cgrates/cdrc3/out</cdr_out_dir>
<cdr_source_id>csv3</cdr_source_id>
<fields>
<field id="tor" filter="^*voice" />
<field id="accid" filter="~3:s/^(\d{2})\.(\d{2})\.(\d{4})\s{2}(\d{2}):(\d{2}):(\d{2})$/$1$2$3$4$5$6/" />
<field id="reqtype" filter="^rated" />
<field id="direction" filter="^*out" />
<field id="tenant" filter="^cgrates.org" />
<field id="category" filter="^call" />
<field id="account" filter="~0:s/^([1-9]\d+)$/+$1/" />
<field id="subject" filter="~0:s/^([1-9]\d+)$/+$1/" />
<field id="destination" filter="~1:s/^([1-9]\d+)$/+$1/" />
<field id="setup_time" filter="4" />
<field id="answer_time" filter="4" />
<field id="usage" filter="~6:s/^(\d+)$/${1}s/" />
</fields>
</configuration>
<configuration section="cdre" type="fwv" id="CDRE-FW1">
<cdr_format>fwv</cdr_format>
<data_usage_multiply_factor>0.0</data_usage_multiply_factor>
<cost_multiply_factor>0.0</cost_multiply_factor>
<cost_shift_digits>0</cost_shift_digits>
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
<mask_length>0</mask_length>
<export_dir>/var/log/cgrates/cdre</export_dir>
<export_template>
<header>
<fields>
<field name="ToR" type="constant" value="10" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="FileType" type="constant" value="SIP" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="LastCdr" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150405" width="12" />
<field name="FileVersion" type="constant" value="01" width="2" />
<field name="Filler2" type="filler" width="105" />
</fields>
</header>
<content>
<fields>
<field name="ToR" type="constant" value="20" width="2" />
<field name="Subject" type="cdrfield" value="subject" width="12" padding="right" mandatory="true" />
<field name="ConnectionNumber" type="constant" value="00000" width="5" />
<field name="CallerId" type="cdrfield" value="~callerid:s/\+(\d+)/00$1/" strip="xright" width="15" padding="right" />
<field name="Destination" type="cdrfield" value="~destination:s/^\+311400(\d+)/$1/:s/^\+311412\d\d112/112/:s/^\+31(\d+)/0$1/:s/^\+(\d+)/00$1/" strip="xright" width="24" padding="right" mandatory="true" />
<field name="TypeOfService" type="constant" value="00" width="2" />
<field name="ServiceId" type="constant" value="11" width="4" padding="right" />
<field name="AnswerTime" type="cdrfield" value="answer_time" layout="020106150405" width="12" mandatory="true" />
<field name="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true" />
<field name="DataCounter" type="filler" width="6" />
<field name="VatCode" type="constant" value="1" width="1" />
<field name="NetworkId" type="constant" value="S1" width="2" />
<field name="DestinationSubId" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\w{5})&quot;/$1/:s/(\w{6})/$1/" width="5" />
<field name="NetworkSubtype" type="constant" value="3" width="1" padding="left" />
<field name="CgrId" type="cdrfield" value="cgrid" strip="xleft" width="16" paddingi="right" mandatory="true" />
<field name="FillerVolume1" type="filler" width="8" />
<field name="FillerVolume2" type="filler" width="8" />
<field name="DestinationSubId" type="cdrfield" value="~cost_details:s/&quot;MatchedDestId&quot;:&quot;.+_(\w{5})&quot;/$1/:s/(\w{6})/$1/" width="5" />
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
<field name="MaskDestination" type="metatag" value="mask_destination" width="1" />
</fields>
</content>
<trailer>
<fields>
<field name="ToR" type="constant" value="90" width="2" />
<field name="Filler1" type="filler" width="3" />
<field name="FileType" type="constant" value="SIP" width="3" />
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
<field name="TotalRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
<field name="TotalDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
<field name="FirstCdrTime" type="metatag" value="first_cdr_atime" layout="020106150405" width="12" />
<field name="LastCdrTime" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
<field name="Filler1" type="filler" width="93" />
</fields>
</trailer>
</export_template>
</configuration>
</document>

View File

@@ -0,0 +1,61 @@
# Real-time Charging System for Telecom & ISP environments
# Copyright (C) ITsysCOM GmbH
#
# This file contains the default configuration hardcoded into CGRateS.
# This is what you get when you load CGRateS with an empty configuration file.
[global]
rpc_json_listen = :2012 # RPC JSON listening address
[rater]
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
[scheduler]
enabled = true # Starts Scheduler service: <true|false>.
[cdrs]
enabled = true # Start the CDR Server service: <true|false>.
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
# cdrstats = # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
[mediator]
enabled = true # Starts Mediator service: <true|false>.
# rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
# cdrstats = internal # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
[cdrstats]
enabled = true # Starts the cdrstats service: <true|false>
#queue_length = 50 # Number of items in the stats buffer
time_window = 1h # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
# metrics = ASR, ACD, ACC # Stat metric ids to build
# setup_interval = # Filter on CDR SetupTime
# tors = # Filter on CDR TOR fields
# cdr_hosts= # Filter on CDR CdrHost fields
# cdr_sources = # Filter on CDR CdrSource fields
# req_types = # Filter on CDR ReqType fields
# directions = # Filter on CDR Direction fields
# tenants = # Filter on CDR Tenant fields
# categories = # Filter on CDR Category fields
# accounts = # Filter on CDR Account fields
# subjects = # Filter on CDR Subject fields
# destination_prefixes = # Filter on CDR Destination prefixes
# usage_interval = # Filter on CDR Usage
# mediation_run_ids = # Filter on CDR MediationRunId fields
# rated_accounts = # Filter on CDR RatedAccount fields
# rated_subjects = # Filter on CDR RatedSubject fields
# cost_intervals = # Filter on CDR Cost
[session_manager]
enabled = true # Starts SessionManager service: <true|false>
switch_type = opensips # Defines the type of switch behind: <freeswitch>
[opensips]
listen_udp = :2020 # Address where to listen for datagram events coming from OpenSIPS
mi_addr = 172.16.254.77:8020 # Adress where to reach OpenSIPS mi_datagram module
[mailer]
# server = localhost # The server to use when sending emails out
# auth_user = cgrates # Authenticate to email server using this user
# auth_passwd = CGRateS.org # Authenticate to email server with this password
# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out

Some files were not shown because too many files have changed in this diff Show More