969 Commits

Author SHA1 Message Date
DanB
24fda5b14b Changelog update 2014-03-25 17:25:15 +01:00
DanB
f3f6bb1e16 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-25 17:19:46 +01:00
DanB
9211c01d69 Fixup tutorial readmes 2014-03-25 17:18:09 +01:00
Radu Ioan Fericean
87481ed520 rename ActionTimings Tag and Id 2014-03-25 18:17:17 +02:00
DanB
5857e63823 Changing documentation index to rc4 2014-03-25 17:07:50 +01:00
DanB
9ebf2573b0 Adding console command: rem_cdrs 2014-03-25 17:05:00 +01:00
DanB
e67db4a434 Tests fixup for exporter 2014-03-25 16:42:45 +01:00
DanB
375bf8c0dd CdrExporter returns stats for successfuly and unsuccessfuly exported CDRs 2014-03-25 16:00:33 +01:00
DanB
6acfa22a04 Adding localtests for RemStoredCdrs 2014-03-25 13:40:06 +01:00
DanB
29e3bd137b Fixup build, removed removeFromDb parameter in cdr_export console command 2014-03-25 13:30:39 +01:00
DanB
f390281f41 API for CdrExporter fixed_width, RemCdr separated from exporter due to buggy scenario discovered 2014-03-24 22:23:03 +01:00
DanB
881db9c1c4 Adding required to fixed_width filter, adding export of the header and trailer, tests 2014-03-24 16:50:57 +01:00
DanB
def252d153 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-23 21:40:17 +01:00
DanB
2bc4e3fb7e Basic implementation of fixed_width cdr export content 2014-03-23 21:17:48 +01:00
Radu Ioan Fericean
e381265ace save dirty balances at the end of debit 2014-03-23 20:21:05 +02:00
DanB
15f2a2cd82 Adding more properties to XmlCdrFields config 2014-03-23 09:14:43 +01:00
DanB
4bb3e745f1 Adding config xml template for exporter with fixed width CDRs together with tests 2014-03-22 22:16:59 +01:00
DanB
4045d022ae Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-22 16:51:03 +01:00
DanB
5e06cafd2f Adding rating aliases, account aliases, shared groups to cache stats struct, improving fs_json tutorial tests and documentation 2014-03-22 16:50:25 +01:00
DanB
a93ecfc049 Adding initial xmlconfig structure with tests 2014-03-22 16:49:06 +01:00
Radu Ioan Fericean
5d0e6b4fee removed superflous if 2014-03-21 18:15:46 +02:00
Radu Ioan Fericean
5de1a83bf5 process all balances on debit (own+shared) 2014-03-21 18:01:28 +02:00
Radu Ioan Fericean
c6de153685 more tests and disabled shared gropup caching 2014-03-20 19:05:31 +02:00
Radu Ioan Fericean
22adfb552f debit from shared group empty balances 2014-03-20 17:27:29 +02:00
Radu Ioan Fericean
b0cd66509c fix for shared get max session 2014-03-20 12:53:36 +02:00
DanB
4d02ede7df Shared balance local tests improvements 2014-03-20 10:39:04 +01:00
DanB
d77c5463ac Adding local test for MaxSessionTime out of shared balance 2014-03-19 21:57:23 +01:00
Radu Ioan Fericean
19b7d0beb7 max session caclulus to consider shared groups 2014-03-19 19:09:25 +02:00
Radu Ioan Fericean
315eddb63f moved locking before debit 2014-03-19 17:32:02 +02:00
Radu Ioan Fericean
acda24e46e ansible variables 2014-03-19 15:06:46 +02:00
Radu Ioan Fericean
af16503069 added profile variables to ansible 2014-03-19 13:06:51 +02:00
Radu Ioan Fericean
822a921a37 more ansible provision commands 2014-03-18 19:41:06 +02:00
DanB
6017827059 Small tweaks to make local tests running with vagrant 2014-03-18 14:55:46 +00:00
DanB
3892d9ed20 LoadTariffPlanFromFolder, tutfscsv tests fixup 2014-03-18 14:09:09 +01:00
Radu Ioan Fericean
5322063103 use *mine_random as default strategy 2014-03-18 11:09:49 +02:00
DanB
e8393b9bcc Modified sample tariffplan for fs_json tutorial 2014-03-18 08:43:07 +01:00
Radu Ioan Fericean
724ebdc039 split rating profile and account aliases 2014-03-17 21:28:25 +02:00
DanB
2dbc80166e Adding sharedGroup loading inside csv and db loaders, tariff plans for fs_json tutorial 2014-03-17 19:10:21 +01:00
DanB
ccac8236f4 Fix test localtest for new rating IdCC 2014-03-17 17:55:46 +01:00
DanB
3c34310ab2 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-17 17:46:57 +01:00
DanB
2ef0f4f492 Adding regexp rules for shared groups csv file 2014-03-17 17:46:53 +01:00
Radu Ioan Fericean
f6741c6d88 Added rating id and destination id to call cost 2014-03-17 18:37:50 +02:00
DanB
62e9d02239 Fix regexp rules to accept aliases 2014-03-17 16:18:52 +01:00
DanB
2e80f8a1f4 Fixup sharedGroup number of columns 2014-03-17 15:58:33 +01:00
DanB
3aecec9bb8 Renaming cdrexporter package into cdre 2014-03-17 10:00:00 +01:00
DanB
e2e3b8d2f2 Local tests for GetCallCostLog API, LogCallCost and GetCallCostLog storage methods 2014-03-16 21:01:27 +01:00
DanB
86317e4b19 Adding apier/cdrs.go file 2014-03-16 19:55:52 +01:00
DanB
f2f46f8054 Adding GetCallCostLog API and console command 2014-03-16 19:51:30 +01:00
DanB
c66f4f2710 Local tests for SetCdr, SetRatedCdr and GetStoredCdrs 2014-03-16 18:49:05 +01:00
DanB
ee31976401 Adding detailed CDR export filters both in storage and APIs 2014-03-16 16:21:18 +01:00
DanB
f6d16cecc5 Adding full CDR template in exported CDRs, using RSRFields 2014-03-16 13:11:08 +01:00
DanB
db433a760f Adding search/replace regexp rules for FreeSWITCH extra fields 2014-03-15 19:17:40 +01:00
DanB
47700a924f Adding RSRField and tests around 2014-03-15 18:48:30 +01:00
DanB
548a4ea64f Adding ReSearchReplace struct 2014-03-15 16:30:17 +01:00
DanB
3227434fd9 Adding tests for search&replace functions 2014-03-15 14:19:04 +01:00
DanB
c344554bbb Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-14 21:14:10 +01:00
DanB
57be22dd87 Adding search and replace regexp utils 2014-03-14 21:14:05 +01:00
Radu Ioan Fericean
a3cc1fedfd use unixnano for random 2014-03-14 19:53:47 +02:00
Radu Ioan Fericean
6685b7b444 add code comment for fscdr body 2014-03-14 19:31:52 +02:00
Radu Ioan Fericean
10b27fa978 better test for random shared balance 2014-03-14 19:30:55 +02:00
Radu Ioan Fericean
e2a50a77fd search whole cdr if extra field not found in variables 2014-03-14 19:21:56 +02:00
DanB
00b47c1367 More local tests for tutorials 2014-03-13 19:50:49 +01:00
DanB
cdd83ea531 Adding local tests for tutfsjson 2014-03-13 18:40:32 +01:00
DanB
d33299f0ab Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-13 17:36:04 +01:00
DanB
5083c7479d Adding re-cache in case of LoadRatingProfile also 2014-03-13 17:35:49 +01:00
DanB
37b21d2bb5 Fixup re-cache in case of SetRatingProfile api 2014-03-13 17:01:02 +01:00
DanB
e09cc8527e Empty call costs in session should not be saved since they break 2014-03-12 19:00:21 +01:00
Radu Ioan Fericean
0855d09fea shared group strategy fully customizable 2014-03-11 18:15:12 +02:00
DanB
c8553e9611 Add 1007 as prepaid account in fs_json tutorial to test shared balances 2014-03-10 11:19:17 +01:00
DanB
d8fb33aee3 Fix cdrs port in fs_json tutorial, add 1006 as prepaid account to test shared balances 2014-03-10 11:17:45 +01:00
DanB
3f30cdb7ed Concurrency fixup on saveOperations in emulation mode 2014-03-08 18:34:22 +01:00
DanB
b9681f2d1c Adding checkConfigSanity integrated in config type 2014-03-08 17:37:02 +01:00
DanB
d6a0825142 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-08 16:22:43 +01:00
DanB
8d61099de1 SessionManager - Multiple sessions emulated out of one request to support scenarios like reseller chains 2014-03-08 16:22:22 +01:00
Radu Ioan Fericean
6ba20aee03 smal fix for csv header 2014-03-07 12:16:27 +02:00
DanB
b28a02512c Adding runids in session manager config 2014-03-06 19:49:24 +01:00
DanB
5ae7a18283 Adding setupTime to CDRs for later stats calculation 2014-03-06 15:47:39 +01:00
DanB
5b10f63c94 Reload cache on SetRatingProfile API 2014-03-05 19:10:27 +01:00
DanB
90e3791c0d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-05 18:34:35 +01:00
Radu Ioan Fericean
fdeecafe37 make sure the aliases are up-to-date after load 2014-03-05 16:54:29 +02:00
Radu Ioan Fericean
71a50dc78e duplicate check for db loader too 2014-03-05 14:56:54 +02:00
Radu Ioan Fericean
8f47264248 check for duplicate account actions 2014-03-05 14:51:18 +02:00
Radu Ioan Fericean
a753a55119 fixed connectFee in max debit issue 2014-03-05 14:17:47 +02:00
DanB
2556fb5e69 Fix in documentation changed default CDRs port which is now 2080 2014-03-05 12:06:09 +01:00
Radu Ioan Fericean
0b0474fa23 fix for MaxDebit bug
max duration was calculated badly
2014-03-04 22:02:20 +02:00
Radu Ioan Fericean
8967c2fa3a fix rounding up problem 2014-03-03 20:37:13 +02:00
DanB
bea8434d99 Adding apier_local_test.cfg file 2014-03-02 18:15:22 +01:00
DanB
bd5b2607e8 More local tests changes to write in /tmp 2014-03-02 18:09:39 +01:00
DanB
62ccb5b9df Automatic starting daemons out of sample configs in localtests, /tmp as output folder to work in non-root mode 2014-03-02 17:34:03 +01:00
DanB
755ab02d55 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-02 12:09:15 +01:00
DanB
cf0179e505 Adding FS_SIP_REQUSER as fallback variable for destination in case of fscdr 2014-03-02 12:09:09 +01:00
Radu Ioan Fericean
bde488c46b ignore vagrant ansible host file 2014-03-02 12:56:14 +02:00
Radu Ioan Fericean
68aefe4912 removed vagrant generated file 2014-03-02 12:56:14 +02:00
DanB
487a1c5da6 Fix tariffplan data and sleep in tutfscsv_local_test 2014-03-02 09:43:27 +01:00
DanB
64711c6b87 Sleep in local tests to allow topup to happen 2014-03-02 09:29:41 +01:00
DanB
781db890d1 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-03-01 10:29:23 +01:00
DanB
7b618f8806 Preparing packaging for rc4 2014-03-01 10:29:16 +01:00
Radu Ioan Fericean
9006c5ed18 removed logs from tests 2014-03-01 00:11:05 +02:00
Radu Ioan Fericean
e6b02b84c0 extra tests for topup after load 2014-03-01 00:02:56 +02:00
Radu Ioan Fericean
b557e2a5a4 fixed connect fee issues 2014-02-28 23:24:55 +02:00
Radu Ioan Fericean
9a06d5e537 fixes for ansible devel 2014-02-28 18:40:39 +02:00
Radu Ioan Fericean
be8ea3bcc9 fixed devel ansible 2014-02-28 18:40:39 +02:00
Radu Ioan Fericean
4833eb94c3 splitted in devel/release 2014-02-28 18:40:39 +02:00
Radu Ioan Fericean
f717bb9542 changed travis rules and removed stats 2014-02-28 18:40:39 +02:00
DanB
2646e8fa6e Local tests to capture toup 2014-02-28 10:46:28 +01:00
DanB
ed313c3222 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-02-28 10:46:02 +01:00
Radu Ioan Fericean
25b15b8f0f map storage has compression too
only needed for better emulating redis
2014-02-28 11:08:03 +02:00
Radu Ioan Fericean
7d9326965d don't overwrite account on load 2014-02-28 10:47:30 +02:00
DanB
52ae44fc34 Fixup sample tariffplans 2014-02-27 17:48:45 +01:00
DanB
a0aa0cc05d Merge branch 'master' of https://github.com/cgrates/cgrates 2014-02-27 17:38:49 +01:00
DanB
b9d52c6401 Fix validation for Actions, now with shared balances 2014-02-27 17:38:42 +01:00
Radu Ioan Fericean
48d1720430 refund credit to shared group accounts 2014-02-27 17:12:59 +02:00
Radu Ioan Fericean
39e93e7fb8 fixses and tests for shared balances 2014-02-27 16:54:11 +02:00
Radu Ioan Fericean
49a3df285b test for shared group members loading 2014-02-27 15:30:29 +02:00
Radu Ioan Fericean
fc91e35433 shared balance fix 2014-02-27 14:04:59 +02:00
Radu Ioan Fericean
95d4af8ab7 more rounding and maxxsession improvement 2014-02-27 12:23:11 +02:00
Radu Ioan Fericean
83dd4efeab more aliases tests 2014-02-27 09:34:48 +02:00
Radu Ioan Fericean
b3dee97bc4 fixses and tests for aliases 2014-02-26 20:17:59 +02:00
Radu Ioan Fericean
074313b0f8 first aliases implementation
tests pending
2014-02-26 18:29:49 +02:00
Radu Ioan Fericean
8d98436656 rounding after debit 2014-02-26 11:14:13 +02:00
Radu Ioan Fericean
477af9467f added load test for shared groups 2014-02-26 11:14:13 +02:00
DanB
1a8e7f4631 Testing tutfscsv including maxDebit and unitsCounter for general usage 2014-02-25 20:52:11 +01:00
DanB
670b748b01 Renaming tag->id and old id->tbid in mysql tables 2014-02-25 13:53:13 +01:00
DanB
ddbd97ad20 Fix merge in tutfscsv_local 2014-02-24 12:47:23 +01:00
DanB
fe4506008f Local test TutFsCsv from config file 2014-02-24 12:41:24 +01:00
Radu Ioan Fericean
0aa5223f78 Merge branch 'master' into shared_balances
Conflicts:
	engine/account.go
	engine/loader_csv_test.go
	engine/storage_interface.go
	engine/storage_map.go
	engine/storage_redis.go
2014-02-21 16:36:48 +02:00
Radu Ioan Fericean
a37dbf0734 renamed Account Type attribute
It's now AllowNegative and it's a boolean (was *prepaid/*postpaid)
2014-02-21 15:52:47 +02:00
Radu Ioan Fericean
7831a53797 renamed USerBalance to Account 2014-02-21 14:37:31 +02:00
Radu Ioan Fericean
f701a42948 renamed action's BalanceId to BalanceType
also added a topup test
2014-02-21 13:14:06 +02:00
Radu Ioan Fericean
164b9f8945 fixed database credentials 2014-02-20 23:12:53 +02:00
Radu Ioan Fericean
a3daecdbc9 first running vagrant-ansible version 2014-02-20 22:13:43 +02:00
Radu Ioan Fericean
a91873800d started ansible vagrant box 2014-02-20 22:00:05 +02:00
Radu Ioan Fericean
f463dd2755 added *daily and *yearly for balance expiration time 2014-02-20 18:07:10 +02:00
Radu Ioan Fericean
f052b14214 added some comments 2014-02-20 17:29:13 +02:00
Radu Ioan Fericean
5f1923d694 Merge branch 'master' into shared_balances 2014-02-18 15:38:03 +02:00
Radu Ioan Fericean
c52b161b73 allow seetting rate subject and expiration with AddBalance api 2014-02-18 15:37:03 +02:00
DanB
d7a0446585 Fixup localtests 2014-02-18 11:06:09 +01:00
DanB
216ae2b767 Fixup install documentation to reflect new repo path 2014-02-18 09:27:28 +01:00
DanB
e60fc8cb96 Capturing the output of the engine in tests 2014-02-18 09:25:10 +01:00
Radu Ioan Fericean
8008c76154 Merge branch 'master' into shared_balances 2014-02-17 18:05:54 +02:00
DanB
d929e14159 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-02-17 16:25:41 +01:00
DanB
dd2eb2f97a session_manager/event interface with field filter, preparing for multiple sessions 2014-02-17 16:25:22 +01:00
Radu Ioan Fericean
e0476a3130 Add members on balance creation 2014-02-17 14:50:35 +02:00
Radu Ioan Fericean
fdc451244a fixed typo 2014-02-17 01:28:50 +02:00
Radu Ioan Fericean
098a1f6d3f fixed account actions ids 2014-02-17 01:22:55 +02:00
Radu Ioan Fericean
75046b1f36 more shared group fixes 2014-02-17 01:16:31 +02:00
Radu Ioan Fericean
dec4ebf2f1 added distinct field parameter 2014-02-17 01:09:37 +02:00
Radu Ioan Fericean
4154a3beef Merge ssh://192.168.0.9/home/rif/Documents/prog/go/src/github.com/cgrates/cgrates into shared_balances 2014-02-17 00:43:04 +02:00
Radu Ioan Fericean
0efb2f1094 load shared groups from csv 2014-02-17 00:42:41 +02:00
Radu Ioan Fericean
5371688158 Merge ssh://192.168.0.9/home/rif/Documents/prog/go/src/github.com/cgrates/cgrates into shared_balances 2014-02-17 00:40:57 +02:00
Radu Ioan Fericean
51ee4091bc shared group fixes 2014-02-17 00:39:15 +02:00
Radu Ioan Fericean
540412033f used local addres in tests 2014-02-16 23:13:27 +02:00
Radu Ioan Fericean
a731a33238 Merge branch 'master' into shared_balances 2014-02-16 22:52:20 +02:00
Radu Ioan Fericean
96092faffc Moved build status up 2014-02-13 13:50:41 +02:00
Radu Ioan Fericean
beea40977c Fixed bad copy paste 2014-02-13 13:24:31 +02:00
Radu Ioan Fericean
ffaeac112c Update README.md 2014-02-13 13:23:27 +02:00
Radu Ioan Fericean
9c46b61c73 Api links point to apier package 2014-02-13 13:17:00 +02:00
Radu Ioan Fericean
1ab852e3cd Update README.md 2014-02-13 13:14:51 +02:00
Radu Ioan Fericean
4e956eaa8c Update README.md 2014-02-13 13:14:38 +02:00
DanB
a5f5274095 Adding config for additional sessions management 2014-02-13 11:55:10 +01:00
DanB
4c894ee9de Adding original apier_local_test 2014-02-13 10:13:12 +01:00
Radu Ioan Fericean
361d75d8cf compressed all GetTP*Ids methods into one 2014-02-13 00:01:58 +02:00
Radu Ioan Fericean
64e79764de build fix 2014-02-12 16:36:56 +02:00
Radu Ioan Fericean
6ad5794bfc added db loader for shared balances 2014-02-12 16:23:03 +02:00
Radu Ioan Fericean
c447fa49dc Merge branch 'master' into shared_balances 2014-02-12 14:18:03 +02:00
DanB
9042c98492 Fixup mediation with rerating 2014-02-11 12:57:00 +01:00
DanB
bd71dc9a4c Test fixups 2014-02-10 20:00:30 +01:00
DanB
944262ccff Adding local_test for mediator rpc method 2014-02-10 18:31:27 +01:00
Radu Ioan Fericean
fc1e7aed5a merged master 2014-02-10 15:12:01 +02:00
DanB
b91fc4ca21 Adding mediator_rpc 2014-02-10 14:08:24 +01:00
Radu Ioan Fericean
b784edf5aa Merge branch 'master' into shared_balances
Conflicts:
	engine/balances.go
	utils/consts.go
2014-02-07 22:32:03 +02:00
Radu Ioan Fericean
914fe77117 rating subject can have multiple predefined values
Valid values can start with *zero + any duration 1m, 15m, 1h10m
2014-02-07 22:26:39 +02:00
Radu Ioan Fericean
77ea2754cb GetMaxSessionDuration fix
should return no more than initial call descriptor duration
2014-02-07 19:54:09 +02:00
Radu Ioan Fericean
cd531849d1 Merge branch 'master' into shared_balances 2014-02-07 19:12:28 +02:00
Radu Ioan Fericean
dadf06f3ef get max session duration method was modifing call descriptor 2014-02-07 19:10:44 +02:00
Radu Ioan Fericean
9e88719e05 fixed build problem 2014-02-07 12:24:52 +02:00
Radu Ioan Fericean
3a3b92e9d6 little cleanup 2014-02-07 11:13:39 +02:00
Radu Ioan Fericean
a9d698e029 Merge branch 'master' into shared_balances
Conflicts:
	engine/balances.go
	engine/userbalance.go
2014-02-07 10:57:29 +02:00
Radu Ioan Fericean
aa99a1e526 make sure connect fee is deducted only on LoopIndex = 0 2014-02-06 19:36:42 +02:00
Radu Ioan Fericean
f154dac933 connect fee changes
The connect fee is take from the first timespan after balance debit.
So if the balance has ratingsubject than that connect fee will be applied.
2014-02-06 18:56:21 +02:00
Radu Ioan Fericean
59f650e3f8 small addition last night fix 2014-02-06 10:15:27 +02:00
Radu Ioan Fericean
462152a76d fixed rating with rating subject bug 2014-02-06 01:34:38 +02:00
Radu Ioan Fericean
3bf3f04cc6 removed connect fee as a separte field in call cost 2014-02-06 01:34:38 +02:00
DanB
9f9174d0cc Console get_maxduration command implementation 2014-02-05 18:51:15 +01:00
DanB
e6ef9f8155 DebitBalance console command, fix apier local_test for GetUserBalances 2014-02-05 17:59:49 +01:00
Radu Ioan Fericean
bea20cf98d fixed history files creation 2014-02-05 14:08:49 +02:00
Radu Ioan Fericean
59e6e945b5 small typo fix 2014-02-05 14:08:37 +02:00
Radu Ioan Fericean
77c326cccf action rating subject may or may not start with a * 2014-02-04 20:54:48 +02:00
Radu Ioan Fericean
709594ffa9 get_balance console renamed to get_balances
returns json formatted userbalance object
2014-02-04 15:28:02 +02:00
Radu Ioan Fericean
58d485f46f small console fixes 2014-02-04 14:40:58 +02:00
DanB
4cbadb4158 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-02-03 19:04:53 +01:00
DanB
caba3af732 Mediation RPC with rerate abilitites, rename RatedCDR -> StoredCdr, adding storage interface tests 2014-02-03 19:04:42 +01:00
Radu Ioan Fericean
727337e617 start shared groups cvs loading
modified shared group structure
2014-02-03 18:50:26 +02:00
Radu Ioan Fericean
20a0ae4449 removed direction and added destinationid to add_balance console command
th consoled is aimed to be used for simple and quick test actions
2014-02-03 17:34:37 +02:00
Radu Ioan Fericean
5f331f3161 add direction parameter for add_balance console command 2014-02-03 15:13:22 +02:00
Radu Ioan Fericean
c7ea2cd263 miminmum match length is now parametrized and can be moved in the confs
Note that it affects the general speed
2014-02-03 10:53:32 +02:00
Radu Ioan Fericean
30a842a6b9 miminmum match length is now parametrized and can be moved in the confs
Note that it affects the general speed
2014-02-01 14:04:12 +02:00
DanB
3f7bc14b44 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-31 19:31:15 +01:00
Radu Ioan Fericean
6fcd794005 moved hist init before populate 2014-01-31 18:49:49 +02:00
Radu Ioan Fericean
750260505c build fix 2014-01-31 17:22:32 +02:00
Radu Ioan Fericean
5c2d1a9c1a Merge branch 'master' into shared_balances 2014-01-31 17:08:44 +02:00
Radu Ioan Fericean
d90e67b966 moved get next start date from shared balances branch 2014-01-31 17:07:36 +02:00
Radu Ioan Fericean
0b7f315d49 match one symbol prefix as well 2014-01-31 17:05:05 +02:00
Radu Ioan Fericean
a239b8d760 bigger refactoring for next start date calculus 2014-01-31 16:37:48 +02:00
Radu Ioan Fericean
0490eaef8c split shared debiting methods minutes/money 2014-01-29 21:54:17 +02:00
Radu Ioan Fericean
ea6c8371d2 Merge branch 'master' into shared_balances 2014-01-29 21:45:05 +02:00
Radu Ioan Fericean
4032274748 fixed next start time test 2014-01-29 18:39:36 +02:00
DanB
edf5007f9e Session manager following api changes in FSock 2014-01-29 11:02:37 +01:00
Radu Ioan Fericean
31eebb7e81 Merge branch 'master' into shared_balances 2014-01-28 13:00:40 +02:00
Radu Ioan Fericean
5d190f0f2a add actions for enabling/disabling user 2014-01-28 13:00:04 +02:00
Radu Ioan Fericean
a5fad89574 added disabling facility for user accounts 2014-01-28 12:22:50 +02:00
Radu Ioan Fericean
23a676da95 got engine from master 2014-01-27 11:46:59 +02:00
Radu Ioan Fericean
69cf1cc896 Merge branch 'master' into shared_balances
Conflicts:
	apier/v1/apier_local_test.go
	apier/v1/tutfscsv_local_test.go
	cmd/cgr-engine/cgr-engine.go
	engine/storage_map.go
	engine/storage_mongo.go
	engine/storage_redis.go
2014-01-27 11:18:25 +02:00
Radu Ioan Fericean
b735688fe5 Remaned to hasdata method 2014-01-27 11:04:25 +02:00
DanB
6a251c2b2c Tutorials config updates for rc4 2014-01-26 10:33:02 +01:00
DanB
21b4c10836 Tests - cdrc/RecordAsRatedCdr ratedcdr/AsRawCdrHttpForm 2014-01-26 09:45:51 +01:00
DanB
1bbe5a0a88 CDRc communicating over internal interface with the CDRs 2014-01-25 20:52:32 +01:00
DanB
8733b5219e Mediator and SessionManager wait for cache to come up before starting over internal interface 2014-01-25 14:28:03 +01:00
DanB
f61aca8d91 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-25 14:03:18 +01:00
Radu Ioan Fericean
3bbb67c72f working variant for history server and gob enc 2014-01-25 11:52:11 +02:00
Radu Ioan Fericean
8177e64f8b new histtory tests 2014-01-25 11:52:11 +02:00
DanB
9210b20924 Fmt on sources :( 2014-01-25 10:49:00 +01:00
DanB
5b424d0e70 Engine components sync via chans 2014-01-25 10:46:21 +01:00
Radu Ioan Fericean
b6dde967f2 samll engine fix 2014-01-24 17:35:57 +02:00
Radu Ioan Fericean
c50c202fe6 Merge branch 'master' into shared_balances
Conflicts:
	apier/v1/apier_local_test.go
	apier/v1/tutfscsv_local_test.go
	cmd/cgr-engine/cgr-engine.go
2014-01-24 10:26:55 +02:00
Radu Ioan Fericean
5f9d18fe0f removed extra log 2014-01-24 09:37:30 +02:00
Radu Ioan Fericean
a31231de09 merge 2014-01-24 09:33:32 +02:00
DanB
ddeb13bcc4 Engine start order, improved logging, default config listening fix 2014-01-23 18:19:03 +01:00
Radu Ioan Fericean
70d0e0171c better historyy agent starting 2014-01-23 18:22:50 +02:00
DanB
ba3cabb7bb Extra log in engine for history 2014-01-23 17:03:43 +01:00
DanB
5d857f1255 Fix local tests 2014-01-23 13:18:00 +01:00
DanB
4f3e91d8ca Default client-server over internal connections, small fixup order of service start 2014-01-23 12:45:13 +01:00
DanB
7c52e1e692 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-23 11:05:06 +01:00
Radu Ioan Fericean
81cc68e0c5 removed exit after cdrs 2014-01-23 11:59:05 +02:00
DanB
f51e0b10e3 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-23 10:51:03 +01:00
Radu Ioan Fericean
999ac0aead added server log messages 2014-01-23 11:44:14 +02:00
DanB
02297d4d36 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-23 10:29:41 +01:00
DanB
95fde288ad Fixup mailer 2014-01-23 10:29:31 +01:00
Radu Ioan Fericean
ea75cd3aa2 cgr-engine main is async again 2014-01-23 11:25:32 +02:00
Radu Ioan Fericean
21875f7ed7 compiation fixes 2014-01-23 11:13:29 +02:00
Radu Ioan Fericean
7697b74713 Merge branch 'master' into shared_balances
Conflicts:
	apier/v1/apier.go
	cmd/cgr-engine/cgr-engine.go
	engine/storage_redis.go
2014-01-23 10:26:29 +02:00
DanB
46b87953da Fix test default config mailer 2014-01-22 23:28:05 +01:00
DanB
5e72e1c528 Adding mail_async action 2014-01-22 22:14:06 +01:00
DanB
baf590dab3 Renaming ActionTimings -> ActionPlans inside tutorial samples 2014-01-22 21:09:20 +01:00
DanB
2c3e8e1584 Adding mailer configuration 2014-01-22 21:06:57 +01:00
Radu Ioan Fericean
962cf17884 updated config file 2014-01-22 21:02:23 +02:00
Radu Ioan Fericean
ac60a39852 removed the async stuff from main and fixed method Lock has wrong number
of ins: 1 issue
2014-01-22 20:55:37 +02:00
Radu Ioan Fericean
0005ba2f68 RPC server changes
rpc server now serves both json and gob, refactored server code into
it's own type
2014-01-22 19:53:30 +02:00
DanB
08e5d8540a callUrlAsync using previous json encoded balance for error logging 2014-01-21 17:52:03 +01:00
DanB
6c96937059 Async call url logs failures 2014-01-21 13:31:53 +01:00
DanB
080395e7ea Adding async call_url action 2014-01-21 13:03:47 +01:00
DanB
2896053199 Renaming ActionsTimings into ActionPlan so we avoid in the future redis store changes as much as possible 2014-01-19 20:14:46 +01:00
DanB
0348be416c ActionTimings -> ActionPlan in APIs, storage tables and redis key 2014-01-19 20:02:32 +01:00
DanB
13707ca377 Increased cgrates version for next packaging 2014-01-18 19:47:00 +01:00
DanB
8ec403f151 Fixup reloading cache keys on csv/db load, removed automatic caching in db/set methods since it makes no sense/trouble in distributed environments 2014-01-18 19:45:23 +01:00
DanB
abd0a00ecb Fix packaging to uncompress freeswitch config 2014-01-18 09:12:59 +01:00
DanB
e97b72a30a Archiving FreeSWITCH tutorial configs since tools like ohloh.net are counting them as CGRateS sources 2014-01-18 08:53:52 +01:00
DanB
a3048a696f Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-15 18:57:44 +01:00
DanB
4065bcb58f Fixup rates tutorial json 2014-01-15 18:57:17 +01:00
Radu Ioan Fericean
3c622d317a warning when duplicating timings 2014-01-15 17:45:39 +02:00
DanB
9f84950287 Localtest for tutfscsv, Monit config for cgrates 2014-01-15 15:24:09 +01:00
DanB
6c82f01a10 Tutorial rates fixup, apier reload cache after loading from file, init script creates rundir 2014-01-15 15:23:32 +01:00
DanB
d8f2fc8521 Fixup config tutorial 2014-01-14 22:34:33 +01:00
DanB
9caed50e0a Adding FS_JSON tutorial configs, fixup action log to dump balance as json, improvments csv tutorial 2014-01-14 21:15:06 +01:00
Radu Ioan Fericean
ea6337d9fa updated engine command 2014-01-14 19:23:05 +02:00
Radu Ioan Fericean
2a1f6ff583 Merge branch 'master' into shared_balances 2014-01-14 19:17:11 +02:00
Radu Ioan Fericean
657100c1e8 I did not run test locally, my bad 2014-01-14 19:16:28 +02:00
Radu Ioan Fericean
b7cdf53858 Merge branch 'master' into shared_balances 2014-01-14 19:11:33 +02:00
Radu Ioan Fericean
4738f7d882 fix for storage interface nil 2014-01-14 19:10:47 +02:00
DanB
2c1b96c6b3 Console AddBalance with Weight 2014-01-14 17:23:53 +01:00
Radu Ioan Fericean
99e269b894 Merge branch 'master' into shared_balances 2014-01-14 18:20:29 +02:00
DanB
187d9476f6 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-14 16:38:03 +01:00
Radu Ioan Fericean
4b0ccb7564 protection against recursive action execution from triggers 2014-01-14 17:26:16 +02:00
DanB
a0d76f8dda Fixup in apier_local_test 2014-01-14 13:36:38 +01:00
DanB
765ab1c600 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-14 12:26:14 +01:00
DanB
e6ffd56479 Console adding Overwrite 2014-01-14 12:26:10 +01:00
Radu Ioan Fericean
d28815bf0a execute action triggers on debit action 2014-01-14 13:24:06 +02:00
DanB
77fa996a7d Fixup cgr-engine 2014-01-14 11:16:02 +01:00
DanB
c0b6562a54 Fixup tutorial init script 2014-01-14 10:37:53 +01:00
DanB
845ecc8fd9 Bug fixup in reloading cache on startup(actions ignored) 2014-01-14 10:18:41 +01:00
DanB
49ab537be8 Init script fixups 2014-01-14 10:09:22 +01:00
Radu Ioan Fericean
c50a61fec5 vet fixing 2014-01-13 22:37:10 +02:00
Radu Ioan Fericean
d765e0090b Merge branch 'master' into shared_balances
Conflicts:
	engine/loader_csv.go
2014-01-13 22:34:19 +02:00
Radu Ioan Fericean
d776219013 added user info to balance for shared balances finding 2014-01-13 22:11:51 +02:00
DanB
9feadce362 Fixups freeswitch rotate script, tutorial config 2014-01-13 20:57:18 +01:00
DanB
9428ffa9c6 Fixups for loader to work with dry_run when destinations file not provided, config fixups, doc fixups 2014-01-13 20:24:36 +01:00
Radu Ioan Fericean
7bc182e374 various go vet fixes 2014-01-13 17:30:06 +02:00
DanB
993c5400aa Static fields in CDRC tutorial example, rotate csv files by script provided 2014-01-13 13:47:25 +01:00
DanB
758f399acb Adding static fields inside cdrc config 2014-01-13 13:15:16 +01:00
DanB
91bae964ba Fixup CDRC to only log on errors but not stop the server, adding freeswitch rotate cdrs script 2014-01-12 19:56:27 +01:00
DanB
43c097aa98 Fixup config 2014-01-12 19:22:18 +01:00
DanB
30681fa7d4 FS-CSV Tutorial updates 2014-01-12 19:15:00 +01:00
DanB
c6bbd32298 Adding pseudoprepaid in session manager un-park 2014-01-12 18:54:04 +01:00
DanB
157ef5359b Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-12 18:37:40 +01:00
DanB
8a323544e8 Adding SMCallMaxDuration setting 2014-01-12 18:37:18 +01:00
Radu Ioan Fericean
f6758d0d0f More fixes for debit loop call duration 2014-01-12 19:34:56 +02:00
Radu Ioan Fericean
fb1c7265f5 Merge branch 'master' into shared_balances 2014-01-12 19:09:12 +02:00
Radu Ioan Fericean
995cfd9785 fixed debit loop callend issue 2014-01-12 19:08:47 +02:00
DanB
d56bec9338 More fixups in the fs_tutorials 2014-01-12 11:02:54 +01:00
DanB
d2ffa364a2 Copyright/version updated, doc links fixups 2014-01-12 10:58:39 +01:00
DanB
47236109b2 Splitting FS tutorials is multiple pages 2014-01-12 10:52:49 +01:00
DanB
7f9681513b Tutorial FS_CSV: adding info 2014-01-12 10:30:28 +01:00
DanB
34f3dc9e0f Tutorial documentation, fs_csv tariffplans refactoring 2014-01-11 20:27:04 +01:00
DanB
0924f4104c Fixup loader to take from datadir, adding tutorials/fs_csv/cgrates configurations 2014-01-11 16:41:06 +01:00
DanB
e4b5c98367 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-10 20:50:30 +01:00
DanB
ae5118dc0b Fixup data path in local tests 2014-01-10 20:49:58 +01:00
DanB
77c7f205b0 Adding fs_csv freeswitch configs for tutorial 2014-01-10 20:39:25 +01:00
Radu Ioan Fericean
ca6957cb48 Merge branch 'master' into shared_balances
Conflicts:
	engine/accountlock.go
2014-01-10 19:04:55 +02:00
Radu Ioan Fericean
8aafd0ec77 better guardmany locking 2014-01-10 19:02:40 +02:00
DanB
9f125e638f Apier: AddAccount -> SetAccount 2014-01-10 12:51:37 +01:00
Radu Ioan Fericean
f393b600d4 better locking for account guard 2014-01-10 11:46:29 +02:00
DanB
2cd548b810 Adding ActionTimings lock in scheduler 2014-01-10 10:44:09 +01:00
DanB
29edca794b Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-10 09:10:10 +01:00
Radu Ioan Fericean
127e7650fa moved locking down south 2014-01-09 22:31:08 +02:00
DanB
66190b615a Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-09 21:18:09 +01:00
Radu Ioan Fericean
0278f08de4 more lock releasing 2014-01-09 22:16:49 +02:00
DanB
84d16bcce5 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-09 21:00:30 +01:00
Radu Ioan Fericean
7ee915e916 avoid keeping mutex locked over scheduler wait 2014-01-09 21:59:41 +02:00
DanB
3208a78965 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-09 20:03:11 +01:00
DanB
98fc855ee0 Overwrite parameter added on AddBalance 2014-01-09 20:03:01 +01:00
Radu Ioan Fericean
1471c47bda started adding test for shared group and created utility methods for
getting shared balances
2014-01-09 20:06:32 +02:00
Radu Ioan Fericean
c8fb090c11 meged master branch 2014-01-09 17:21:27 +02:00
Radu Ioan Fericean
23a95ed890 added mutex for schedular loop 2014-01-09 17:11:34 +02:00
Radu Ioan Fericean
1c2d2116fd little improvement 2014-01-09 16:57:44 +02:00
DanB
12e896576c Adding Apier tests for cdrserver, newly added commands for account triggers and account timings 2014-01-09 13:32:52 +01:00
DanB
79497e8970 APIs - locking balance and action triggers when operating on them, adding reloadScheduler attribute in some methods 2014-01-09 10:44:38 +01:00
DanB
07dd19a9d5 Adding Get and Rem APIs for AccountTriggers 2014-01-08 21:30:15 +01:00
Radu Ioan Fericean
3f16c1c12d debit minutes logic 2014-01-08 22:15:11 +02:00
DanB
3f506b50c8 Fixup Get and Rem ActionTiming API methods 2014-01-08 20:36:29 +01:00
DanB
b7d9da842b Adding AccountActionTiming Get and Remove APIs 2014-01-08 14:00:20 +01:00
Radu Ioan Fericean
9854ce7068 fixed build 2014-01-07 22:38:38 +02:00
Radu Ioan Fericean
58ddae9b70 Started shared balances logic 2014-01-07 22:34:40 +02:00
DanB
afe845a8aa Enhancing cdrexporter with dry_run, console export_cdrs command 2014-01-07 20:27:45 +01:00
DanB
99a9b2c987 Fixup cdrc to support freeswitch_csv type (ignore .files ending with .csv) 2014-01-06 12:09:51 +01:00
DanB
caa2ed7776 Config to default tor, tenant and subject towards more logical fields 2014-01-06 10:46:29 +01:00
DanB
1a53359c5f Formatting doc default configuration 2014-01-05 19:53:43 +01:00
DanB
b8075c2835 Adding updated default configuration inside documentation 2014-01-05 19:45:09 +01:00
DanB
2a23a29b25 Further formatting on postinstall doc 2014-01-05 19:39:26 +01:00
DanB
4481b170eb Fixup git link 2014-01-05 19:34:18 +01:00
DanB
b122a2a748 Doc formating 2014-01-05 19:32:03 +01:00
DanB
4e4d411a6a Installation doc updated for .rc3 2014-01-05 19:29:06 +01:00
DanB
6b2e0786d2 Adding db setup script 2014-01-05 18:23:07 +01:00
DanB
fdeab3cb09 Merge branch 'master' of https://github.com/cgrates/cgrates 2014-01-05 15:52:44 +01:00
DanB
7099b40609 Debian packaging refactored, ready for nightly packages 2014-01-05 15:42:55 +01:00
Radu Ioan Fericean
3bc4823e1d handled term signals 2014-01-03 20:34:46 +02:00
Radu Ioan Fericean
fa2ded95bc changed pid argument o string (path to file) 2014-01-03 14:00:02 +02:00
Radu Ioan Fericean
eb3bed151d write pid file from cgr-engine 2014-01-02 22:37:58 +02:00
Radu Ioan Fericean
72a415b924 action timing next date fixes 2014-01-01 19:33:18 +02:00
DanB
b8c83e7752 Fixup postinst script 2014-01-01 17:47:24 +01:00
DanB
9f45635de0 CdrE config, documentation fixup 2013-12-31 11:57:19 +01:00
DanB
4092baea56 Adding cgr-tester tool 2013-12-29 17:20:11 +01:00
DanB
5024944a2b History basic documentation, FsCgr tests fixup for localtime compliance, SaveInterval config on history server parsed as time.Duration 2013-12-29 14:17:38 +01:00
DanB
ec5417d6e4 Adding CDRC brief documentation 2013-12-29 11:14:44 +01:00
DanB
2b19fcd408 Finishing fsCdr.AsRatedCdr implementation with tests 2013-12-28 15:58:19 +01:00
DanB
8064491481 Adding share config methods and tests 2013-12-28 10:34:06 +01:00
DanB
9d5ed81791 Console command: get_cache_age 2013-12-27 20:05:11 +01:00
DanB
4eee6ddd0e Adding static field values for multiple mediation in configuration, fmt on sources 2013-12-27 20:02:47 +01:00
DanB
7f8f981085 Small fixups 2013-12-27 11:48:17 +01:00
DanB
b3a3c1f599 Adding runid in logdb 2013-12-27 10:26:02 +01:00
DanB
756affe31a Cache stats console command 2013-12-26 21:22:11 +01:00
DanB
1db3c550b1 Multiple mediation on same RawCDR, renamed CDR interface into RawCDR 2013-12-26 21:21:29 +01:00
DanB
e76b94c285 Adding get_cache_stats console command 2013-12-23 19:31:44 +01:00
DanB
75d24d0fd1 Adding GetCacheStats and GetCachedItemAge Apier methods 2013-12-23 19:23:10 +01:00
DanB
e19b8bc4cf Adding SetRatingProfile method, renaming old SetRatingProfile into LoadRatingProfile and SetRatingPlan into LoadRatingPlan 2013-12-23 16:54:21 +01:00
DanB
c04bc753fd Adding SetActions and SetActionTimings Apier methods 2013-12-23 14:26:33 +01:00
DanB
d60a5f1b79 RatedCDR maintaining CDR interface, Cdrc csv reader does not longer stops in case of row error, further test related fixups 2013-12-22 19:44:45 +01:00
DanB
1950551833 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-22 18:35:30 +01:00
DanB
8e95225b99 Completing the implementation of the CDR client with tests 2013-12-22 18:35:16 +01:00
DanB
c38179c01a Cdrc further refactoring 2013-12-22 13:06:40 +01:00
DanB
ca3b13651f Adding cdrc together with config dependencies and tests 2013-12-20 22:57:45 +01:00
Radu Ioan Fericean
08be8d1bb5 cutting extra decimals 2013-12-20 14:11:47 +02:00
DanB
d8977fc504 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-19 20:01:31 +01:00
DanB
2ba458524e CDRS with datetime instead of timestamp, adding run_ids in mediator config section 2013-12-19 20:01:19 +01:00
Radu Ioan Fericean
a74a201735 seting increment cost to 0 for increments payed using free minutes 2013-12-19 18:44:58 +02:00
Radu Ioan Fericean
6fb2ca37c2 better rates rounding 2013-12-19 18:30:48 +02:00
DanB
74f027189a Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-19 12:01:30 +01:00
DanB
990000f246 Fixup default config for different subsystems databases 2013-12-19 12:01:24 +01:00
Radu Ioan Fericean
4330e9dd59 round increment cost 2013-12-19 12:56:55 +02:00
DanB
3598b7b505 Fixup spanstress tool 2013-12-18 20:32:05 +01:00
DanB
17af9c312a Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-18 20:05:58 +01:00
DanB
f3d0c49803 Finishing implementation of different database handles for rating vs accounting subsystems 2013-12-18 20:02:47 +01:00
Radu Ioan Fericean
f7d7a34c17 typo 2013-12-18 15:38:04 +02:00
Radu Ioan Fericean
6d543f5d86 db loader stats 2013-12-18 15:37:19 +02:00
Radu Ioan Fericean
18273a920e accounting stats 2013-12-18 15:31:20 +02:00
Radu Ioan Fericean
f136f1e67d more statistics 2013-12-18 15:27:46 +02:00
Radu Ioan Fericean
4638bad4d1 replaced storageGetter with dataStorage 2013-12-17 22:37:51 +02:00
Radu Ioan Fericean
e03bbc9b96 started accounting storage 2013-12-17 21:19:38 +02:00
Radu Ioan Fericean
522ef1cf9c commented out the test taht sometimes fails on drone.io 2013-12-17 19:57:44 +02:00
Radu Ioan Fericean
cd9eaecb83 changed the way the destinations are cached 2013-12-17 19:33:04 +02:00
Radu Ioan Fericean
806570cbaf added back set caching 2013-12-17 18:14:32 +02:00
Radu Ioan Fericean
c0d7cf0001 small fixes 2013-12-17 18:02:54 +02:00
Radu Ioan Fericean
d69378a626 started new destination caching 2013-12-17 17:51:38 +02:00
Radu Ioan Fericean
3e4067b4b0 started statistics 2013-12-17 16:42:16 +02:00
Radu Ioan Fericean
dbe8bde7bf optimized prefixes container 2013-12-16 19:09:56 +02:00
Radu Ioan Fericean
d0284bf5e1 standard span stress 2013-12-16 15:27:57 +02:00
Radu Ioan Fericean
41dd86ca9a more optimizations for large destination keys 2013-12-16 15:17:20 +02:00
Radu Ioan Fericean
609f6271df added method for finding cache key age 2013-12-15 20:43:46 +02:00
Radu Ioan Fericean
aa2360f756 started reademe for stress testing tools 2013-12-14 17:08:58 +02:00
Radu Ioan Fericean
243ebdb7fa fixed compile error 2013-12-14 16:58:35 +02:00
Radu Ioan Fericean
022c426a20 further caching and optimizations 2013-12-14 16:51:28 +02:00
Radu Ioan Fericean
5a929dfeb0 some memory profiling and optimization 2013-12-14 15:20:58 +02:00
Radu Ioan Fericean
237508e590 performance enhancements 2013-12-14 12:44:42 +02:00
Radu Ioan Fericean
59543849d4 updated all stress test code 2013-12-13 22:56:16 +02:00
Radu Ioan Fericean
8a36ac72b9 improved refund testing and prepared stres test for profiling 2013-12-13 22:43:46 +02:00
Radu Ioan Fericean
fac3fa5025 renamed test_local to local_test 2013-12-13 17:27:38 +02:00
Radu Ioan Fericean
950a357127 more fixes and test for credit refounding 2013-12-13 17:26:14 +02:00
Radu Ioan Fericean
796caa1c7f fixes for refunded credit 2013-12-13 16:31:11 +02:00
Radu Ioan Fericean
a57686990c another precache fix 2013-12-13 16:21:55 +02:00
Radu Ioan Fericean
d99c0c076c fixed max debit function 2013-12-13 12:56:33 +02:00
Radu Ioan Fericean
b6e5ea82e0 substract connect fee for get max session time 2013-12-12 22:24:08 +02:00
Radu Ioan Fericean
895bf6b6c1 fixed a max session bug 2013-12-12 19:36:19 +02:00
Radu Ioan Fericean
da2ac4ea28 added some logging for max session time 2013-12-12 19:12:50 +02:00
Radu Ioan Fericean
c68660367b improved max session duration (used to check initial connect) 2013-12-12 15:45:49 +02:00
Radu Ioan Fericean
76af5ddf17 one more debit test 2013-12-11 16:39:17 +02:00
Radu Ioan Fericean
690751e3c2 try travis go version 1.2 2013-12-11 14:50:42 +02:00
Radu Ioan Fericean
00be23afb1 Merge branch '12cleanup' 2013-12-11 14:49:33 +02:00
Radu Ioan Fericean
d5f4417005 better rate subject monet debit 2013-12-11 14:48:16 +02:00
Radu Ioan Fericean
6c30c98836 Merge branch 'debit' 2013-12-10 20:24:40 +02:00
Radu Ioan Fericean
6ff0d17d5f refactored debit credit method, now modular and better tested 2013-12-10 20:24:06 +02:00
Radu Ioan Fericean
3611494936 before rewrite 2013-12-10 20:24:06 +02:00
Radu Ioan Fericean
1c4c27b2bf refactored debit credit method, now modular and better tested 2013-12-10 20:22:28 +02:00
DanB
7809fa4e8e Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-10 17:43:38 +01:00
DanB
d95dc46e65 Some more apier local tests 2013-12-10 17:43:32 +01:00
Radu Ioan Fericean
395931aba8 fixed save dusration compilation error 2013-12-09 12:59:32 +02:00
Radu Ioan Fericean
b122090421 added save period for history server 2013-12-09 12:49:56 +02:00
Radu Ioan Fericean
6f9a3ab4c0 before rewrite 2013-12-09 12:25:43 +02:00
DanB
71ca9d8fe3 Adding apier.GetRatingPlans and various local tests 2013-12-06 20:49:00 +01:00
Radu Ioan Fericean
3257010f32 added cache entries count and added prefix for cache keys 2013-12-05 13:08:32 +02:00
DanB
f0fbe79fca Adding test for insufficient balance 2013-12-05 10:44:36 +01:00
DanB
e90960e7fc Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-04 20:58:18 +01:00
DanB
b5d938a767 Validator bug fixup for DestinationRates 2013-12-04 20:53:38 +01:00
DanB
a3d4b9dd8b Completing validators basic tests 2013-12-04 20:46:39 +01:00
Radu Ioan Fericean
10dea0c55d show error on not enough credit when debiting 2013-12-04 21:26:17 +02:00
DanB
131997507d Testing regexp validators 2013-12-04 20:16:45 +01:00
DanB
086d1e7ab2 Adding load_helper_test file 2013-12-04 17:46:11 +01:00
Radu Ioan Fericean
389a81ae4c better locking for history server 2013-12-04 16:03:53 +02:00
Radu Ioan Fericean
300153f961 better *log action 2013-12-04 12:23:33 +02:00
Radu Ioan Fericean
e6ae067db1 reverted some unnecessary optimization 2013-12-04 12:12:24 +02:00
Radu Ioan Fericean
51379946d9 skip the general balance from counter 2013-12-04 11:58:53 +02:00
Radu Ioan Fericean
95b6cbbdc8 refactored unit counters 2013-12-04 11:44:48 +02:00
DanB
3fb00f8fea Doc formatting fixup 2013-12-03 17:34:09 +01:00
DanB
4de7dd3c26 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-03 17:03:46 +01:00
DanB
1388e7caab Testing new doc format for api timings 2013-12-03 17:03:30 +01:00
Radu Ioan Fericean
66c6efab96 fixed debit bug 2013-12-03 17:03:20 +02:00
DanB
92f9849380 Adding realcalls_test file 2013-12-03 15:00:06 +01:00
DanB
5b34dcebd4 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-03 12:29:21 +01:00
DanB
14b9c5d6c9 Adding call_url to tests in apier local tests 2013-12-03 12:29:16 +01:00
Radu Ioan Fericean
8482dde7e8 no longer creates an empty balance at account init 2013-12-03 12:46:56 +02:00
DanB
c0a48ad33a Merge branch 'master' of https://github.com/cgrates/cgrates 2013-12-03 11:24:57 +01:00
DanB
f1e4b57b2c Fixup loader_csv to load call_url actions 2013-12-03 11:24:50 +01:00
DanB
2ffaf09ea1 Fixup Actions regexp validation to accept call_url action 2013-12-02 17:37:35 +01:00
Radu Ioan Fericean
51c3a8b2ff better 1.2 msgpack code 2013-12-02 14:44:55 +02:00
Radu Ioan Fericean
eebfa68375 fixed action timing next date bug 2013-12-02 14:21:01 +02:00
DanB
148ef2e5a1 Small fixup at pkg folder 2013-12-02 11:20:05 +01:00
Radu Ioan Fericean
cc27c6a1a1 fixed local test script 2013-11-29 20:25:28 +02:00
DanB
92594be302 Small modifications in debian pkg 2013-11-29 19:23:56 +01:00
Radu Ioan Fericean
bb9f8bfd7b added fixed width format for cdrs and modified test local to call test.sh script 2013-11-29 19:53:50 +02:00
Radu Ioan Fericean
490e8043d6 more indenting 2013-11-29 19:17:14 +02:00
Radu Ioan Fericean
69200d00e8 format console api command response 2013-11-29 19:02:47 +02:00
Radu Ioan Fericean
453f4c9f33 added more safety locking 2013-11-29 18:55:14 +02:00
Radu Ioan Fericean
9527757790 using goroutines for redis history saving 2013-11-29 14:35:12 +02:00
DanB
fc4696cdaf Some more debug messages removal 2013-11-28 18:15:48 +01:00
DanB
0c6e524c78 Removing debug messages for call calculation 2013-11-28 17:58:15 +01:00
DanB
3b8d9c9082 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-11-28 17:50:52 +01:00
DanB
52d91e2fb4 Adding debian packaging rules to run under cgrates user once started 2013-11-28 17:50:45 +01:00
Radu Ioan Fericean
0b5ca3fe36 missed one method call 2013-11-28 18:20:00 +02:00
Radu Ioan Fericean
b669d1d326 Added bool parameter for searching datadb for cached values or not 2013-11-28 18:09:24 +02:00
DanB
6138a12974 Improved logging for the cache system, better help message for console get_cost 2013-11-28 15:42:29 +01:00
DanB
8e92c51da2 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-11-27 19:52:18 +01:00
DanB
8bae6e2de7 Adding get_cost to console commands 2013-11-27 19:44:27 +01:00
Radu Ioan Fericean
859890f0b4 fixed two caching issues 2013-11-27 20:13:25 +02:00
Radu Ioan Fericean
f203feab20 removed alternative redis driver 2013-11-27 18:03:49 +02:00
Radu Ioan Fericean
ed863123f6 renamed flush option to flushdb 2013-11-27 17:48:42 +02:00
DanB
b45200ccd3 Fixup in scenario file 2013-11-27 15:08:22 +01:00
DanB
d4ea254bb5 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-11-27 14:48:19 +01:00
DanB
fef08d8a27 Fixups loader reload cache order and history logs 2013-11-27 14:48:11 +01:00
Radu Ioan Fericean
9c693e21d0 added new dependency to update shell scripts 2013-11-27 15:04:26 +02:00
Radu Ioan Fericean
865c3656fd using hoisie redis driver 2013-11-27 15:02:44 +02:00
Radu Ioan Fericean
bd4a19ae16 copy destinationa and subject to callcost 2013-11-27 15:02:07 +02:00
DanB
c380fcb118 Adding -scheduler parameter on cgr-engine, adding local tests for Apier reload scheduler, reload cache and getCost 2013-11-25 19:19:50 +01:00
DanB
fd12134596 Same bug fix also in loader_db 2013-11-25 16:50:37 +01:00
DanB
83eb650c97 Bug fixup in loader 2013-11-25 16:40:36 +01:00
DanB
472d036d15 Reverting to individual TPRatePlan load 2013-11-25 16:32:24 +01:00
DanB
d3ddffd55e Various bug fixups, LoadRatingProfileFiltered loads now also RatingPlan if defined 2013-11-25 13:22:53 +01:00
DanB
ade79936b5 Adding ApierV1.LoadTariffPlanFromFolder API together with it's local tests 2013-11-24 20:21:03 +01:00
DanB
5ff600fe18 Adding LoadAll method to loader so we can compress the code from Apier 2013-11-24 19:20:35 +01:00
DanB
fcb98adcf7 Buffer writter for multiple values in sql queries, finished storage interface cleanup 2013-11-24 16:31:42 +01:00
DanB
6e65376bec LoadAccountActionsFiltered fixed to load more than one acccountActions profile, added test_local.sh script 2013-11-22 14:14:54 +01:00
DanB
4fb7c404c1 Adding local tests for ApierV1.SetAccountActions 2013-11-21 20:43:41 +01:00
DanB
6215289a37 Adding local tests for ApierV1.TPActionTriggers 2013-11-21 20:17:47 +01:00
DanB
3d523128e2 LoadRatingProfileFiltered can now load more than one ratingProfile 2013-11-21 19:21:43 +01:00
DanB
11932fd58c Adding local tests for ApierV1.TPActionTimings 2013-11-21 19:00:33 +01:00
DanB
5ac4949f88 Adding local tests for ApierV1.TPActions 2013-11-21 18:40:16 +01:00
DanB
4a6ea0ed35 Small code compression in console 2013-11-21 18:04:42 +01:00
DanB
f69af88bd6 Adding local tests for Load*Filtered methods in datadb 2013-11-21 17:59:50 +01:00
DanB
5d0a4cf470 Changing GetRatingProfileByTag into GetRatingProfileFiltered; local tests for ApierV1.SetRatingPlan and ApierV1.SetRatingProfile 2013-11-21 11:52:49 +01:00
DanB
fa6f91f608 ApierV1.TPRatingProfile local tests 2013-11-20 22:38:04 +01:00
DanB
a585f39b04 ApierV1.TPRatingPlan local tests 2013-11-20 19:48:07 +01:00
DanB
66815639af ApierV1.TPDestinationRate local tests, moved local tests into *_local_* files in order to recognize them better 2013-11-20 19:28:39 +01:00
DanB
8d991672ab Adding ApierV1.TMDestination local tests 2013-11-20 18:20:30 +01:00
DanB
52b60db6b2 Adding the apier_test file again 2013-11-20 14:47:21 +01:00
DanB
c7c4aefe0d Apier local tests, added -rater parameter to cgr-engine command to be able to enforce starting of the rater/apier out of command line, useful mostly for test units 2013-11-20 14:42:15 +01:00
DanB
87ea266c4a Go fmt on sources 2013-11-19 20:06:53 +01:00
DanB
a20d6258e2 Loader does not longer needs db connections on -dry_run 2013-11-19 20:02:45 +01:00
DanB
30d91c9747 Completing local tests between load from csv and stordb into redis 2013-11-19 18:12:15 +01:00
DanB
87b6f9c12e Local tests for load from storDb 2013-11-19 17:19:04 +01:00
DanB
a1bbe1c7a6 Dry run for loader 2013-11-19 16:51:53 +01:00
DanB
47415dadfc Adding local tests for TPImporter into stordb 2013-11-19 16:45:12 +01:00
DanB
509f4a87cd Adding local csv loader tests 2013-11-19 15:44:17 +01:00
DanB
58e26a46ce Adding RemTPData SQL tests 2013-11-18 22:13:04 +01:00
DanB
34b577c74d Some more methods cleanup in storDb 2013-11-18 20:30:15 +01:00
DanB
38f6b69193 Removing Exists methods on storDb since they are no longer in use 2013-11-18 20:16:49 +01:00
DanB
d5c81818ee scriptPath in test storage 2013-11-18 20:00:42 +01:00
Radu Ioan Fericean
fb9483693f copy rating info on timespan split 2013-11-18 20:07:10 +02:00
Radu Ioan Fericean
b4f34a77cc added matched subject and matched prefix to timespan 2013-11-18 19:49:55 +02:00
Radu Ioan Fericean
4b6b7758f8 small fix for session manager 2013-11-18 19:38:45 +02:00
Radu Ioan Fericean
a483d2304e tests for rating info activation times 2013-11-18 19:36:54 +02:00
DanB
352540ab04 Adding local tests, available via flag -local 2013-11-18 16:29:25 +01:00
DanB
4e0ccbfa19 Unifying fallback subjects key between csv and db, first match between loaded data from csv and from stordb 2013-11-18 10:28:27 +01:00
DanB
f38f04f6c5 Various loader and apier fixes 2013-11-17 23:06:20 +01:00
DanB
f5257b46a5 Completing TP Apis with SetTPAccountActions with duplications and remove 2013-11-16 20:51:35 +01:00
DanB
b50f950474 ApierV1 TPActionTriggers set and remove implementation 2013-11-16 17:02:27 +01:00
DanB
198b604407 ApierV1 TPActionTimings set with duplicated and remove 2013-11-16 16:39:32 +01:00
DanB
210c52f041 ApierV1 TPActions - remove checking for existing, allow duplicates 2013-11-16 16:21:03 +01:00
DanB
6b5923f86f Merge branch 'master' of https://github.com/cgrates/cgrates 2013-11-15 22:43:24 +01:00
DanB
3a26ba86ea TPRatingProfile data remodeling, apis with remove and set without duplication checking: TPDestinationRates, TPRatingPlans, TPRatingProfiles 2013-11-15 22:42:45 +01:00
Radu Ioan Fericean
9df187dd8e better rating information finding 2013-11-15 20:26:15 +02:00
DanB
9cf1aed058 ApierV1 SetTPRate and RemTPRate implementation 2013-11-15 17:50:53 +01:00
DanB
f904973d25 Adding SetDuration method in RatingSlot 2013-11-15 17:31:17 +01:00
DanB
adf015af5c Merge branch 'master' of https://github.com/cgrates/cgrates 2013-11-15 14:12:50 +01:00
DanB
b95d21fa4e RatingPlan.Timing going private to hide it from jsonrpc 2013-11-15 14:12:38 +01:00
Radu Ioan Fericean
d05cd92a8d added RemKey method for cache 2013-11-15 14:28:57 +02:00
DanB
719ed3ef51 Set on TPDestinations with update on duplicates, remove api for TPDestinations 2013-11-15 12:48:14 +01:00
DanB
751ec1e4c3 Adding remove method on tptimings, modified SetTPTiming to update on exists 2013-11-15 12:00:52 +01:00
DanB
c2af82e8b0 Console command to reload cache, documentation for apier on cache and scheduler, various small fixes 2013-11-14 19:35:26 +01:00
DanB
8b29a35b96 Adding reload_cache command to cgr-console, reload_scheduler fixup 2013-11-14 17:32:47 +01:00
DanB
65d828a2c7 Adding destination and rating plans filtering for automated cache reload from loader 2013-11-14 13:31:30 +01:00
DanB
26bb99bd64 Replacing DESTINATION and RATING_PLAN consts with their prefix versions for compressing code 2013-11-14 13:03:15 +01:00
DanB
408047dd1c Resyncing structs from apis to the ones in storage 2013-11-14 12:58:13 +01:00
Radu Ioan Fericean
c075d8182a fixed field names 2013-11-14 09:41:52 +02:00
Radu Ioan Fericean
d6238ba5f7 moved dataseries to utils 2013-11-14 09:39:00 +02:00
Radu Ioan Fericean
8189bbbf36 using utils types to load data 2013-11-14 09:39:00 +02:00
DanB
ef9f5fe612 ExistsData method on dataDb, ApierV1.ReloadCache method, cgr-loader calling ReloadCache if configured 2013-11-13 19:27:44 +01:00
DanB
4c19a2078b Merge branch 'master' of https://github.com/cgrates/cgrates 2013-11-13 10:09:30 +01:00
DanB
86bcbac221 Adding dataDb checks for destinations and rating plans on csv loader 2013-11-13 10:04:51 +01:00
Radu Ioan Fericean
20ad7a8487 cleaned exists functions 2013-11-13 00:35:29 +02:00
Radu Ioan Fericean
c6752be776 started work on rating plan activation time handling 2013-11-13 00:08:19 +02:00
Radu Ioan Fericean
5e7f7f6663 added get rating plan API method 2013-11-13 00:08:19 +02:00
DanB
13ce865b6f Destinations considered from dataDb in case of missing from storDb on load 2013-11-12 19:30:49 +01:00
DanB
64a0cda8c1 Mediator swapping uuid fixed 2013-11-12 19:06:15 +01:00
DanB
0b1d9c2da4 Reusing of predefined rating plans in rating profiles 2013-11-12 13:43:00 +01:00
DanB
41d0bb80ac Changing DestRateTimings into RatingPlans 2013-11-12 12:25:02 +01:00
DanB
7bf11a216d Changing DestRateTimings into RatingPlans 2013-11-12 12:24:53 +01:00
DanB
bb340210f1 Fixup cgr-spanstrss 2013-11-12 10:34:43 +01:00
DanB
ce1c22f142 Activate precaching only for rater 2013-11-12 10:18:33 +01:00
DanB
3614ed2624 Another small fixup in the documentation 2013-11-10 18:44:33 +01:00
DanB
d7854524c9 Small fixup in the documentation 2013-11-10 18:43:21 +01:00
DanB
6f00f9b4c2 Adding ApierV1.ExportCsvCdrs documentation 2013-11-10 18:40:29 +01:00
DanB
3790591eb2 Fixup of cdrexporter documentation 2013-11-10 17:50:25 +01:00
DanB
99fc749c1c Adding CDRExporter documentation 2013-11-10 17:42:50 +01:00
DanB
194c52db2d Adding test units for CDR CSV exporter 2013-11-10 16:55:52 +01:00
DanB
84fbf30376 Implementation of ApierV1.ExportCsvCdrs 2013-11-10 14:37:20 +01:00
DanB
25db02a6fb Shifting GetAllRatedCdrs into filtered GetRatedCdrs to prepare it for exporter 2013-11-10 09:01:35 +01:00
DanB
ec6de7e901 Implemented GetAllRatedCdr in store, renamed StorCDR into RatedCDR, changed time_answer into answer_timestamp in cdr_primary table for better go compatibility 2013-11-09 10:22:49 +01:00
DanB
4a5d228d81 Adding files containing the storCdr 2013-11-08 18:45:20 +01:00
DanB
092853d571 Adding StorCdr type, removing unused CDR interface methods 2013-11-08 18:37:07 +01:00
DanB
0229adde44 Adding sample cdr generation with curl 2013-11-07 18:21:33 +01:00
DanB
ab68dcab79 Removing section numbering in case of advanced section of the documentation 2013-11-07 17:47:35 +01:00
DanB
09b3de0474 Adding documentation for CDR server 2013-11-07 17:41:48 +01:00
DanB
458a8f8628 Adding datetime support for answer_time in cgrCdr 2013-11-07 17:41:17 +01:00
DanB
3eda5495ea Adding CGR-CDR handler 2013-11-07 12:35:30 +01:00
Radu Ioan Fericean
bb7b144f84 updated rating profile API 2013-11-06 17:06:58 +02:00
Radu Ioan Fericean
b87399c123 fixed build error 2013-11-05 20:38:39 +02:00
Radu Ioan Fericean
455ed9e547 no precaching for loader (precache parameter) 2013-11-05 20:16:39 +02:00
DanB
68a010a476 Minor variable corrections 2013-11-04 20:32:18 +01:00
DanB
131bc22609 Renaming default sample tariff plans to reflect latest code changes 2013-11-04 19:42:05 +01:00
DanB
01005b06f3 Updating storage tables to reflect latest code 2013-10-31 13:42:52 +01:00
Radu Ioan Fericean
3ae3cadfec fixed double prefix bug 2013-10-30 11:21:25 +02:00
Radu Ioan Fericean
70571e15d0 logged precaching error 2013-10-30 11:05:52 +02:00
Radu Ioan Fericean
fb80c6c2a8 destinations using string and compression 2013-10-29 16:57:55 +02:00
Radu Ioan Fericean
4f7501a24c moved alternate redis drivers to alt_redis branch 2013-10-29 16:38:48 +02:00
Radu Ioan Fericean
61d777b1eb a little more logging 2013-10-28 20:05:02 +02:00
Radu Ioan Fericean
848281697e Added pre-cache for destinations and rating plans 2013-10-28 20:02:51 +02:00
Radu Ioan Fericean
d9b0461746 using destination contains prefix method 2013-10-28 18:56:23 +02:00
Radu Ioan Fericean
566447a0a0 re-added caching for destinations and moved cache to storage classes 2013-10-28 15:25:45 +02:00
Radu Ioan Fericean
f965e2ef72 switched to zlib with faster read 2013-10-24 21:01:20 +03:00
Radu Ioan Fericean
386e927487 using gzip instead of snappy 2013-10-24 20:34:15 +03:00
Radu Ioan Fericean
151d42cc7f some test with gzip and zlib 2013-10-24 20:21:07 +03:00
Radu Ioan Fericean
dbed53fbbf rating plan optimization 2013-10-24 19:25:34 +03:00
Radu Ioan Fericean
a4559a68ea rating plan optimization 2013-10-24 19:22:55 +03:00
Radu Ioan Fericean
b343d89293 added rating plans history 2013-10-22 14:13:28 +03:00
Radu Ioan Fericean
60c8469477 added snappy for updates 2013-10-21 22:48:25 +03:00
Radu Ioan Fericean
d8aeb0ea47 added snappy compression for rating plans in redis 2013-10-21 22:45:51 +03:00
Radu Ioan Fericean
0e0bfe7af0 more fixes for msgpack and go 1.1.2 2013-10-21 22:27:24 +03:00
Radu Ioan Fericean
9e7b195ec6 fix for msgpack 2013-10-21 19:42:45 +03:00
Radu Ioan Fericean
3466fa103b refactored rating plans structure 2013-10-21 19:18:56 +03:00
Radu Ioan Fericean
d03ff0a5cd newsubject tests and fixes 2013-10-15 14:56:39 +03:00
Radu Ioan Fericean
0573e98d21 added bitdeli 2013-10-14 21:27:52 +03:00
DanB
dd03d344f6 Docs updates to reflect API changes 2013-10-14 09:53:01 +02:00
Radu Ioan Fericean
e1ce1f5358 fixed radix and redigo 2013-10-12 01:15:28 +03:00
Radu Ioan Fericean
260c27861c Merge branch 'master' into redis 2013-10-11 23:16:34 +03:00
Radu Ioan Fericean
c756c799f5 some redis tests 2013-10-11 23:15:55 +03:00
Radu Ioan Fericean
824302a8f5 Merge branch 'master' of github.com:cgrates/cgrates 2013-10-11 20:18:51 +03:00
Radu Ioan Fericean
d608c6fe07 improved long timespans splitting 2013-10-11 20:15:47 +03:00
DanB
6f8e318bfc TP API docs updated 2013-10-10 13:33:57 +02:00
DanB
f22f5a1bd9 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-10-09 20:34:43 +02:00
DanB
a22260b317 Small fixups 2013-10-09 20:34:39 +02:00
Radu Ioan Fericean
5b4e7a1234 quick fix 2013-10-09 20:05:51 +03:00
Radu Ioan Fericean
54ab6074a6 more loading fixes and more tests 2013-10-09 20:02:13 +03:00
DanB
afe0dbc5cb Cleaning up weight from mysql and sample data files 2013-10-09 11:42:33 +02:00
DanB
c9c4a484eb Fix build error 2013-10-09 11:06:25 +02:00
DanB
9c6de26fa5 DBData encoding configuration 2013-10-09 10:41:51 +02:00
Radu Ioan Fericean
47c06835ea fixed typo 2013-10-08 22:12:34 +03:00
Radu Ioan Fericean
c0accc3d5e better loading (hopefully :) 2013-10-08 21:57:32 +03:00
Radu Ioan Fericean
56b4a3f0a4 fix for bad timespan 2013-10-08 15:27:04 +03:00
Radu Ioan Fericean
48ccb6e930 even more logging 2013-10-08 14:10:17 +03:00
Radu Ioan Fericean
f286e0a189 Merge branch 'master' of github.com:cgrates/cgrates 2013-10-08 13:18:05 +03:00
Radu Ioan Fericean
f2a9154b41 simplified mediator call duration and more logging 2013-10-08 13:16:18 +03:00
DanB
b7a87d1e59 Action bug fixup, fix cost field in stor db 2013-10-08 11:57:06 +02:00
Radu Ioan Fericean
c59dc78f01 get cost call duration fix 2013-10-07 22:28:04 +03:00
Radu Ioan Fericean
88f789cea9 fixed broken test 2013-10-07 20:04:35 +03:00
Radu Ioan Fericean
4428c09748 ore tests and small fixes 2013-10-07 18:44:20 +03:00
Radu Ioan Fericean
97540fcf8c use matched subject in call cost information 2013-10-07 17:49:44 +03:00
Radu Ioan Fericean
617c3f9591 more tests 2013-10-07 17:03:00 +03:00
DanB
036e363ca8 BUG fixup - StorDB close looping 2013-10-07 15:19:59 +02:00
DanB
f1d2bc6164 Renaming RateSubject into RatingSubject to better reflect it's purpose 2013-10-06 19:18:41 +02:00
DanB
df1cd36f50 Bug fixup in mediator not loading the cdrDb 2013-10-06 18:51:37 +02:00
DanB
dbdad3e3ce Fixing default import files, SQL correction in tp_actions tables 2013-10-06 17:34:24 +02:00
Radu Ioan Fericean
483a13f983 fixed span stress 2013-10-06 11:39:07 +03:00
Radu Ioan Fericean
76d7e02402 merged work on new subject debiting 2013-10-06 11:25:23 +03:00
Radu Ioan Fericean
f5059e55fa started minute new subject 2013-10-06 11:21:16 +03:00
Radu Ioan Fericean
33f24534b3 finished split by increment refactoring 2013-10-04 22:15:25 +03:00
Radu Ioan Fericean
85027f0764 new subject for monetary balances 2013-10-04 22:13:37 +03:00
Radu Ioan Fericean
7632a8f14e tests passing on get max session time 2013-10-04 16:18:28 +03:00
Radu Ioan Fericean
c889eafb20 removed special price 2013-10-03 20:14:45 +03:00
Radu Ioan Fericean
8bd4ac1eba msgpack fixes 2013-10-02 22:33:36 +03:00
Radu Ioan Fericean
7db0919be0 started removing special rates and adding new rate subject 2013-10-02 22:30:42 +03:00
Radu Ioan Fericean
fc00528007 removed unused import 2013-10-02 22:12:01 +03:00
Radu Ioan Fericean
5a6df95133 updated msgpack codec 2013-10-02 22:09:21 +03:00
Radu Ioan Fericean
6cd7edd8a2 refound money 2013-10-02 20:09:42 +03:00
Radu Ioan Fericean
09238f7355 spliting for money/minutes separation 2013-10-02 17:30:10 +03:00
Radu Ioan Fericean
552aa0904f using DestinationContainsPrefix method 2013-09-30 22:07:39 +03:00
Radu Ioan Fericean
eec68807ba db loading hopefully the same as csv 2013-09-26 18:47:29 +03:00
Radu Ioan Fericean
94addca3e5 implemented call url action
it will send a POST request to the action ExtraParameters string url with the json encoded user balance as the body
2013-09-26 13:08:03 +03:00
Radu Ioan Fericean
6c35f6e340 improved csv loading 2013-09-25 21:52:06 +03:00
Radu Ioan Fericean
17b982bdeb new debit shaping up 2013-09-24 18:50:44 +03:00
Radu Ioan Fericean
cd98141fbb still working on balance subject debit 2013-09-23 19:26:40 +03:00
Radu Ioan Fericean
3b98a75bab Merge branch 'shared_balances' into redis 2013-09-18 22:52:18 +03:00
Radu Ioan Fericean
8c1ad3de86 reverted to map for tests 2013-09-18 18:09:56 +03:00
Radu Ioan Fericean
f90b9e7aa3 destinations are now stored in sets 2013-09-18 17:55:07 +03:00
Radu Ioan Fericean
9c5715bb7b redis drivers 2013-09-15 23:07:45 +03:00
Radu Ioan Fericean
1ff176cf47 more name refactorings 2013-09-14 20:09:59 +03:00
Radu Ioan Fericean
8cda25cadb one more action 2013-09-14 18:10:00 +03:00
Radu Ioan Fericean
fa788103cf added test for all csv loaders 2013-09-14 17:58:52 +03:00
Radu Ioan Fericean
bb22d37fc6 working on csv load testing 2013-09-14 15:23:52 +03:00
Radu Ioan Fericean
e18420ea23 rated seconds slice 2013-09-12 21:09:42 +03:00
Radu Ioan Fericean
a35f5ed480 interval and activation profile refactoring 2013-09-12 16:24:57 +03:00
Radu Ioan Fericean
a1612f5507 implemented timespan expanding 2013-09-10 14:45:55 +03:00
Radu Ioan Fericean
1ee31ca004 started rating unit implementation 2013-09-09 22:16:07 +03:00
Radu Ioan Fericean
372fbe0e10 started destination hash storage and colapsed debit actions 2013-09-09 20:45:46 +03:00
Radu Ioan Fericean
2f6b1f3f84 tests passing with new balance struct 2013-09-05 23:28:04 +03:00
Radu Ioan Fericean
f3f9d03c80 before switching to MINUTES+OUTBOUND 2013-09-05 20:17:14 +03:00
Radu Ioan Fericean
931c6efb1a converted minutebucket to balance, compiled but test fail 2013-09-05 19:14:22 +03:00
Radu Ioan Fericean
f82a2c58a8 better balance Equal function 2013-09-05 16:55:06 +03:00
Radu Ioan Fericean
48d1953f9a minor doc corrections (hope drone.io will build the master branch if I make a commit on it) 2013-09-04 19:32:42 +03:00
Radu Ioan Fericean
5516c8ff1f started bucket balance merge 2013-09-04 18:18:49 +03:00
Radu Ioan Fericean
3731fb1977 merged storage interfaces split 2013-08-31 13:01:30 +03:00
Radu Ioan Fericean
8f804b8bc5 refactorings, started groups 2013-08-30 19:02:05 +03:00
Radu Ioan Fericean
5083b54bcd removed unused dependencies 2013-08-30 13:31:44 +03:00
Radu Ioan Fericean
637e146ba2 absolute/percent const rename 2013-08-30 13:26:06 +03:00
Radu Ioan Fericean
7edcb9fd6e added round to minute util function and reverted to debit only available credit for prepaid 2013-08-29 15:51:03 +03:00
Radu Ioan Fericean
9004ef1075 using new msgpack package 2013-08-28 23:01:59 +03:00
Radu Ioan Fericean
20f1bf27c3 debit minutes even if balance goes negative 2013-08-28 18:02:54 +03:00
Radu Ioan Fericean
17fe5b3351 rating logic documentation 2013-08-27 19:58:04 +03:00
Radu Ioan Fericean
47442dda3c small typo 2013-08-27 19:01:30 +03:00
Radu Ioan Fericean
ed6c1fc151 logdb, cdrdb and loaddb are all stordb 2013-08-27 16:01:44 +03:00
Radu Ioan Fericean
5acbdc6934 cleaned unimplemented methods from db adaptors 2013-08-27 15:07:50 +03:00
Radu Ioan Fericean
411af6ba74 splited storage interface in multiple interfaces 2013-08-27 14:48:01 +03:00
Radu Ioan Fericean
1bfaa3a52f simplified code and corrected rating profile id 2013-08-26 17:06:30 +03:00
Radu Ioan Fericean
8a6d7a897b Merge branch 'master' of github.com:cgrates/cgrates 2013-08-26 14:13:44 +03:00
Radu Ioan Fericean
9cce7c6449 checking destination ids exist on setting rating profile api call 2013-08-26 14:13:27 +03:00
Radu Ioan Fericean
860f00080d missed reload command file 2013-08-26 10:56:02 +03:00
DanB
3c6b5051af Merge branch 'master' of https://github.com/cgrates/cgrates 2013-08-25 22:32:36 +02:00
DanB
ba3d3c450d TP Docs 2013-08-25 22:32:16 +02:00
Radu Ioan Fericean
c651aabb32 killed internal serialization and started using msgpack 2013-08-23 19:54:06 +03:00
Radu Ioan Fericean
08c4386e65 added reload scheduler to console commands 2013-08-23 17:09:37 +03:00
Radu Ioan Fericean
c3102e8375 reverted too using time sleep instead of a ticker because of the loop execution order 2013-08-23 15:54:29 +03:00
DanB
bf772ada3f Fixup timings 2013-08-22 21:05:03 +02:00
DanB
609c821a50 Adding partial new documentation structure for tariff plans 2013-08-22 20:45:24 +02:00
Radu Ioan Fericean
af64c50fe6 hoping for a minute bucjet fix 2013-08-22 17:08:40 +03:00
Radu Ioan Fericean
2448507578 check the string instead of slice 2013-08-22 16:58:34 +03:00
Radu Ioan Fericean
9409a4b65e more debugging 2013-08-22 16:41:04 +03:00
Radu Ioan Fericean
09bbf2af8f wanted to make sure the log is with the atest version 2013-08-22 16:21:12 +03:00
Radu Ioan Fericean
c2913b6fe9 simplifyed code for minute buckets 2013-08-22 14:36:27 +03:00
Radu Ioan Fericean
99992ab38a small fix for minute buckets serialization 2013-08-22 14:30:26 +03:00
Radu Ioan Fericean
7b559fa68d even better get next date for timed actions 2013-08-22 11:44:03 +03:00
Radu Ioan Fericean
154a9ab00b Merge branch 'master' of github.com:cgrates/cgrates 2013-08-21 19:24:34 +03:00
Radu Ioan Fericean
85a7e434b7 fixed the firts of month bug 2013-08-21 19:24:12 +03:00
DanB
30d731d4c5 Removing ExpiryTime from mandatory Actions parameters since there are specific Actions who do not define it 2013-08-21 14:57:50 +02:00
Radu Ioan Fericean
1c912c8974 fixed typo 2013-08-20 19:57:27 +03:00
Radu Ioan Fericean
c14489222b fix for empty minute buckets 2013-08-20 19:55:21 +03:00
DanB
b3e9a84f6f Fixup ExpiryTime in actions, string now 2013-08-19 18:09:23 +02:00
DanB
86919f831f Bug fixes SQL params 2013-08-18 20:47:19 +02:00
DanB
54704b8cdd Renaming api param RateProfileId into RatingProfileId, bug fixup in sql query related to SetRatingProfile API 2013-08-18 19:55:23 +02:00
DanB
38df076010 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-08-18 17:06:32 +02:00
DanB
edd2c704de TP APIs are now accepting string as time.Duration values, modified mysql table structures to increase *id fields to 64 2013-08-18 17:06:03 +02:00
Radu Ioan Fericean
b894ab8236 faster history record search 2013-08-16 13:46:57 +03:00
DanB
b799dcc8ae BUG Fix SetTPRatingProfile API 2013-08-16 11:40:41 +02:00
Radu Ioan Fericean
c639c13647 travis to use go 1.1 2013-08-14 18:02:25 +03:00
Radu Ioan Fericean
e27bcaabf3 fix on extra pointer and added tests for missing fields checker 2013-08-14 17:53:40 +03:00
DanB
a67e410ed8 History server reconnects 2013-08-12 14:08:47 +02:00
Radu Ioan Fericean
a9f03893b5 doc updates 2013-08-07 22:26:41 +03:00
DanB
d31536c249 Fixup create_tariffplan_tables 2013-08-07 17:47:07 +02:00
Radu Ioan Fericean
48fb53ac08 running load tests 2013-08-07 18:18:44 +03:00
Radu Ioan Fericean
305dca0fc9 some name refactoring 2013-08-07 15:37:17 +03:00
Radu Ioan Fericean
cb20ae167f moved rated units in rate group and transformed it in duration 2013-08-07 15:11:05 +03:00
Radu Ioan Fericean
7a38b39631 more switch relative time fixes 2013-08-07 14:20:14 +03:00
Radu Ioan Fericean
c209dbfb9f using time.Duration for rateincrements and groupinterval 2013-08-07 13:37:25 +03:00
Radu Ioan Fericean
0785874357 call duration fixes and switch relative start/end time 2013-08-07 13:37:25 +03:00
DanB
f07a0cc885 Fixing mediator and related for postpaid rating into storDb 2013-08-07 06:20:56 +02:00
DanB
84dbe167f5 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-08-06 18:09:13 +02:00
Radu Ioan Fericean
e027a2a6a1 nil user balance fix 2013-08-05 18:06:33 +03:00
Radu Ioan Fericean
5dcbb28dd6 added more console commands 2013-08-05 17:58:42 +03:00
Radu Ioan Fericean
9a0dc72d0d added execute action 2013-08-05 17:02:05 +03:00
Radu Ioan Fericean
52afbd376e added versioning for api and two new console commands: get_destination and add_balance (also improved set_balance) 2013-08-05 16:33:47 +03:00
Radu Ioan Fericean
8a58dfd3dd history sync improvements 2013-08-05 12:47:41 +03:00
DanB
be1c2fbb7a Adding freeswitch autoload_configs folder to automatically support json_cdr 2013-08-05 11:39:26 +02:00
DanB
f002c2a224 Updating prepaid1centpersec tariffplan scenario, removing data/rates in favour of tariffplans folder 2013-08-05 11:31:28 +02:00
DanB
609bbdcb2a Merge branch 'master' of https://github.com/cgrates/cgrates 2013-08-04 23:17:34 +02:00
DanB
8000887051 Config and code updates to properly handle CDRS and Mediator variations 2013-08-04 22:01:36 +02:00
DanB
a814b1cc17 Adding sanity checks for CDRS working with internal mediator 2013-08-04 21:48:25 +02:00
Radu Ioan Fericean
2d08f0a31e set -1 cost on error 2013-08-04 22:32:11 +03:00
DanB
b04f276fb6 Removing configuration to enable cdr handlers since filtering is based on url and no extra resources are consumed by handlers 2013-08-04 21:29:51 +02:00
DanB
5f730090fc Adding some formatting 2013-08-04 21:06:43 +02:00
DanB
ff6b55c111 Adding CDRSEnable in config 2013-08-04 21:05:27 +02:00
Radu Ioan Fericean
2156f05131 history flood/loop protection 2013-08-04 20:21:43 +03:00
Radu Ioan Fericean
882a567fe1 added history client for cgr-loader 2013-08-04 18:58:23 +03:00
DanB
58c16a5f47 Errors fixup in sql queries 2013-08-04 16:45:43 +02:00
Radu Ioan Fericean
8b6639f186 better error reporting in the serializer 2013-08-04 15:51:04 +03:00
Radu Ioan Fericean
88db48470d integrated hstory into cgr-engine command 2013-08-04 15:39:53 +03:00
DanB
864b7eca5a Adding extra fields test 2013-08-04 13:42:43 +02:00
DanB
05c6ee4756 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-08-04 13:18:12 +02:00
DanB
d1fd3e4e09 Changing OUT-> *out everywhere, tests fscdr 2013-08-04 13:17:55 +02:00
Radu Ioan Fericean
9411759bc9 added history configuration options 2013-08-03 20:19:05 +03:00
DanB
80e5fa7403 Saving datetime as unixtime in MySQL queries 2013-08-02 22:04:43 +02:00
DanB
06ed29ddd4 Dropping support for urlencode in json fscdr. This can be disabled via FS module 2013-08-02 21:49:45 +02:00
Radu Ioan Fericean
13e8f53230 no longer expand the dates for *any 2013-08-02 19:33:47 +03:00
Radu Ioan Fericean
8c8f003239 added rating profile history handler 2013-08-02 18:57:54 +03:00
DanB
10f46f348a Fixups pkg 2013-08-02 17:36:47 +02:00
DanB
230571c632 Fix run script in the skel folder 2013-08-02 17:10:57 +02:00
Radu Ioan Fericean
8fac5a1ff1 cstmid to tenant 2013-08-02 17:46:07 +03:00
Radu Ioan Fericean
13347532d6 url decode extra fields 2013-08-02 17:13:02 +03:00
Radu Ioan Fericean
b6b5c11033 same OUT problem with cdrs 2013-08-02 17:03:15 +03:00
Radu Ioan Fericean
4a9c4eff43 fs event returning *out 2013-08-02 17:01:42 +03:00
Radu Ioan Fericean
d0e185c47a one history recor per line 2013-08-02 15:17:55 +03:00
Radu Ioan Fericean
457151ecce added history tests 2013-08-02 13:05:19 +03:00
Radu Ioan Fericean
ed5ebc2efc added some more tests 2013-08-02 12:58:33 +03:00
Radu Ioan Fericean
1771d9de3a history test passing 2013-08-02 12:54:35 +03:00
Radu Ioan Fericean
b57d321e30 made test pass for the moment 2013-08-01 18:20:41 +03:00
Radu Ioan Fericean
5941823138 Merge branch 'master' of github.com:cgrates/cgrates 2013-08-01 18:20:17 +03:00
Radu Ioan Fericean
6c5d73d41a more refactoring and added first test 2013-08-01 18:19:18 +03:00
Radu Ioan Fericean
63a1fc6f31 refeactored history 2013-08-01 17:25:26 +03:00
Radu Ioan Fericean
8a457618be working on store 2013-08-01 17:03:22 +03:00
DanB
5bb67111b2 Fixup packaging script 2013-08-01 08:59:46 +02:00
DanB
1b759af9e2 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-08-01 08:20:45 +02:00
DanB
e5652558bc Adding debian packaging script 2013-08-01 08:16:03 +02:00
Radu Ioan Fericean
606d053844 better parse date 2013-07-31 14:45:46 +03:00
Radu Ioan Fericean
59e474bf68 expiration time fixes 2013-07-31 10:05:13 +03:00
Radu Ioan Fericean
1d3ce69c5d added activation time with + and * stuff 2013-07-30 23:06:31 +03:00
Radu Ioan Fericean
11e7eed149 added +duration and *monthly for expiration time 2013-07-30 22:42:51 +03:00
Radu Ioan Fericean
78d2ee4e28 fixed tests 2013-07-30 22:15:35 +03:00
Radu Ioan Fericean
67d9c6f64d merge Dan's work 2013-07-30 21:54:27 +03:00
Radu Ioan Fericean
d112b53dda working on duration expire time 2013-07-30 21:47:17 +03:00
DanB
2f733525b2 Finalizing TPCSVImporter with AccountActions method 2013-07-30 19:35:20 +02:00
DanB
c60b26c0c4 TPCSVImporter ActionTriggers 2013-07-30 17:03:16 +02:00
DanB
d483a6669f TPCSVImporter ActionTimings 2013-07-30 12:52:58 +02:00
DanB
d4017890ec TPCSVImporter Actions, store refactoring, small API params changes 2013-07-29 21:18:02 +02:00
DanB
ddc2240c67 TPCSVImporter RatingProfiles, changed RateProfile into RatingProfile in APIs for consistency 2013-07-29 18:21:13 +02:00
DanB
9887799492 TPCSVImporter DestRateTiming, store refactoring 2013-07-29 15:32:07 +02:00
DanB
f1d94aebaf TPCSVImporter - DestinationRates implementation 2013-07-29 14:18:50 +02:00
DanB
1a1403ace1 Few more methods on TPCSVImporter, API TPRates modifications to include GroupInterval 2013-07-29 12:40:23 +02:00
DanB
63d9612932 Another way of mapping csvtpimport function, trying to fix tests on 1.0.3 2013-07-27 13:29:34 +02:00
DanB
37c893d4f8 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-27 12:26:57 +02:00
DanB
ee15ce5056 Tests fix 2013-07-27 12:26:42 +02:00
DanB
0be3f8b342 Small fixups cgr-loader 2013-07-27 12:09:07 +02:00
DanB
26d270c7c1 Updating cgr-loader to dataDb 2013-07-27 11:39:31 +02:00
DanB
e5f0af9fb6 Refactoring validators to match new csv file structure 2013-07-27 10:25:18 +02:00
DanB
9f28fa0d24 Partial TPCSVImporter, TPCSVFileParser 2013-07-25 20:29:47 +02:00
Radu Ioan Fericean
ff9f8c82af started work on generic cdr 2013-07-25 13:51:22 +03:00
Radu Ioan Fericean
64c1cd67f0 implemented global rounding method and precision for get cost 2013-07-25 12:16:15 +03:00
Radu Ioan Fericean
6afd7ee19f one more test for group split 2013-07-24 19:10:50 +03:00
Radu Ioan Fericean
946a496a46 more tests for splitting 2013-07-24 19:08:51 +03:00
Radu Ioan Fericean
af811d669e moved rate increments in the group scope 2013-07-24 14:13:58 +03:00
DanB
352b58eccc Engine path in daemontools script 2013-07-24 12:54:17 +02:00
Radu Ioan Fericean
c1139bc2a8 changed importer to to reflect new package name 2013-07-24 13:41:57 +03:00
Radu Ioan Fericean
6c79fd2092 rater lib changed to engine 2013-07-24 13:38:45 +03:00
Radu Ioan Fericean
3d9fc28d82 changed cgr-rater tool name to cgr-engine 2013-07-24 13:13:08 +03:00
DanB
14adc49f2e Partial TPImporter implementation 2013-07-24 12:08:58 +02:00
DanB
3ddc63f4f5 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-24 09:29:29 +02:00
Radu Ioan Fericean
42cb2d9b4f goup rates (not fully tested) 2013-07-23 17:33:17 +03:00
DanB
10b40e2652 StorDb defaults to MYSQL 2013-07-23 16:28:26 +02:00
Radu Ioan Fericean
c14be129ed loading rates 2013-07-23 16:58:00 +03:00
Radu Ioan Fericean
6d3e011634 some rating optimization and all hardcoded values using *lowercase 2013-07-23 16:58:00 +03:00
DanB
2c7eb3593e New config parameters - RaterRoundingMethod and RaterRoundingDecimals, renaming logDb into storDb in config 2013-07-23 13:59:17 +02:00
DanB
8845b4b310 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-23 11:23:19 +02:00
DanB
84acdd737b Centralizing ConfigureDatabase and csv filenames 2013-07-23 11:23:11 +02:00
Radu Ioan Fericean
2805190f45 small management api improvements 2013-07-22 13:54:30 +03:00
Radu Ioan Fericean
bcc0370c31 fix for setaction on non exiting account 2013-07-22 12:36:00 +03:00
DanB
eb906ed87a Fixup 2013-07-20 22:15:20 +02:00
DanB
081ddce3f3 Formatting SetAccountActions api documentation 2013-07-20 22:05:19 +02:00
DanB
06fef1536c SetAccountActions API fixups 2013-07-20 18:34:37 +02:00
DanB
77abee97db Adding TPAccountActions API documentation 2013-07-20 14:29:44 +02:00
DanB
e4530fdd0e Adding TPAccountActions api methods 2013-07-20 13:59:13 +02:00
DanB
22f9297248 Adding ActionTriggers APIs documentation 2013-07-19 20:12:35 +02:00
DanB
30de8c1c95 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-19 19:49:50 +02:00
DanB
b8a9d77575 Adding ActionTriggers api methods 2013-07-19 19:49:44 +02:00
Radu Ioan Fericean
383bba3412 small docs fix 2013-07-19 20:16:24 +03:00
Radu Ioan Fericean
210f290e67 SetAccountAction API and filter for RESET_TRIGGERS 2013-07-19 20:06:42 +03:00
Radu Ioan Fericean
6c8d2c7187 improved expiration time 2013-07-19 20:06:41 +03:00
DanB
17597375dc Adding ActionTimings api documentation 2013-07-19 10:47:51 +02:00
DanB
092a80d9d4 Adding ActionTimings api methods 2013-07-19 09:37:07 +02:00
DanB
e7de9222d1 Reversing the order of ThresholdValue and ThresholdType definition in csv files 2013-07-18 14:16:02 +02:00
DanB
39923c3d5e Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-18 14:04:26 +02:00
DanB
6969606096 Actions related API data definition 2013-07-18 13:59:47 +02:00
Radu Ioan Fericean
d5c4c717f3 rounding methods in csv loader and implemented triggered type 2013-07-18 13:44:19 +03:00
DanB
72fd2984ad Unique keys for actiontriggers and account actions 2013-07-18 12:30:30 +02:00
DanB
d75d1475fc Adding mysql unique key for actions 2013-07-18 12:01:43 +02:00
DanB
2eb8c093ad Finishing TPActions API methods and adding their documentation 2013-07-18 11:36:45 +02:00
DanB
0a3adf326e Adding SetTPAction api method 2013-07-17 19:37:48 +02:00
DanB
c0881d977d Adding GetDestination api back 2013-07-17 14:55:30 +02:00
DanB
09f5db1a31 Fixup doc 2013-07-17 14:41:21 +02:00
DanB
e29f295cb0 Adding SetRatingProfile API documentation 2013-07-17 14:36:19 +02:00
DanB
5ba6c6ccc9 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-16 21:18:23 +02:00
DanB
ebbadb50dd Apier.SetRatingProfile method fixups 2013-07-16 21:18:08 +02:00
Radu Ioan Fericean
b6bfc1745c expiration balances and minute buckets 2013-07-15 21:06:14 +03:00
DanB
8f3b146f71 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-15 15:26:47 +02:00
Radu Ioan Fericean
6d595eee2d expiration date for minutebuckets 2013-07-15 12:53:46 +03:00
Radu Ioan Fericean
6f0ba9449b reverted multiple destinations 2013-07-15 11:33:42 +03:00
Radu Ioan Fericean
6aafc8cf5f working on dimed balances 2013-07-15 11:31:36 +03:00
Radu Ioan Fericean
45fd41a1a2 Implement balance counters for number of minutes with a special discount per group of destinations 2013-07-14 20:29:40 +03:00
Radu Ioan Fericean
460b2470f1 Merge branch 'master' of github.com:cgrates/cgrates 2013-07-14 17:59:22 +03:00
Radu Ioan Fericean
ae3a6a786e rounding methods and decimals for interval 2013-07-14 17:59:14 +03:00
DanB
c39b2d5a84 Fixup docs 2013-07-12 20:41:49 +02:00
DanB
2be2bc97a0 Adding API support for rounding functions at rate level 2013-07-12 16:09:41 +02:00
DanB
526b50ed12 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-12 11:26:17 +02:00
DanB
1ada3d929a Adding TPRateProfile documentation 2013-07-12 11:26:09 +02:00
Radu Ioan Fericean
3123b44501 Merge branch 'master' of github.com:cgrates/cgrates 2013-07-12 12:24:08 +03:00
Radu Ioan Fericean
34bc6107ea use rating profile id for loading from storedb 2013-07-12 12:23:56 +03:00
DanB
b7c6314a09 Finished implementing TPRateProfile APIs 2013-07-12 08:40:28 +02:00
DanB
7b1b278c87 Adding SetRatingProfile api method 2013-07-11 21:34:31 +02:00
DanB
aa1c2021d3 Adding TPDestRateTiming APIs and attached documentation 2013-07-11 14:44:54 +02:00
DanB
341c96552d Fixup doc 2013-07-10 17:34:25 +02:00
DanB
89af3fa82e Adding TPDestinationRates API documentation 2013-07-10 17:31:44 +02:00
DanB
f3dbbf77fc Adding TPDestinationRate APIs 2013-07-10 16:58:57 +02:00
DanB
33febd9285 Fixup documentation tprates 2013-07-10 15:20:16 +02:00
DanB
edbfd1b436 loader_csv fixup for tests to pass 2013-07-10 12:46:02 +02:00
DanB
57c47987e2 Adding Apier TPRates documentation 2013-07-10 12:23:51 +02:00
DanB
f443d384d0 Adding Apier Get methods for TPRate 2013-07-10 11:55:12 +02:00
DanB
d3e16d3712 Apier.SetTPRates with RateSlots 2013-07-10 10:36:51 +02:00
DanB
60bd0c0a08 Implemented Apier.SetTPRate method 2013-07-10 08:13:09 +02:00
DanB
b5092ebabe Implemented Apier.SetTPRate method 2013-07-10 08:13:04 +02:00
DanB
2b025a3fc9 Adding TP APIs 2013-07-09 15:48:49 +02:00
DanB
2aaac1b9e1 Fixup doc 2013-07-09 13:28:19 +02:00
DanB
b5a5b4631f Fixup doc 2013-07-09 13:27:24 +02:00
DanB
49ecf65fdc Refactoring TPDestination Api documentation 2013-07-09 13:24:26 +02:00
DanB
8ecf706086 Adding TPTiming APIs together with documentation attached 2013-07-09 13:15:02 +02:00
DanB
7d06363c10 Adding attrs structure to getTpDestinationIds 2013-07-08 17:38:34 +02:00
DanB
dfa91442d4 Reverting tpdestinations 2013-07-08 17:32:36 +02:00
DanB
d2baf4fa08 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-08 15:16:11 +02:00
DanB
56b5ce9a49 Refactoring tpdestination documentation based on new data template 2013-07-08 15:15:53 +02:00
Radu Ioan Fericean
0401809d0d fix for rates loading 2013-07-08 14:45:19 +03:00
DanB
f0d96157e4 Adding documentation for Apier.GetTPDestination and Apier.GetTPDestinationIds methods 2013-07-07 20:45:41 +02:00
DanB
6152ce6925 Merge branch 'master' of https://github.com/cgrates/cgrates 2013-07-07 19:46:28 +02:00
DanB
5d62eea4f2 Adding Apier.SetTPDestination documentation 2013-07-07 19:46:10 +02:00
Radu Ioan Fericean
2ed059ff7d loading rates from db 2013-07-07 14:10:07 +03:00
Radu Ioan Fericean
389c173471 csv loading rates 2013-07-06 12:04:01 +03:00
DanB
25df692872 Adding GetTPDestinationIds API method 2013-07-05 08:46:54 +02:00
DanB
db62f2cc85 Finishing Get and Set for TPDestinations 2013-07-04 19:20:55 +02:00
DanB
7e308dca52 Adding Apier.SetTPDestination 2013-07-03 23:01:37 +02:00
Radu Ioan Fericean
00e48bc23a new setratingprofile api command 2013-07-03 14:24:56 +03:00
Radu Ioan Fericean
8a7d8c7fd1 fixed typo 2013-07-01 15:42:48 +03:00
Radu Ioan Fericean
f89eecaef8 writing the loaded rating profile to db 2013-06-29 17:35:27 +03:00
Radu Ioan Fericean
14ea78cc34 added sql storage 2013-06-28 22:54:34 +03:00
Radu Ioan Fericean
aff4813757 unified sql storages and worked on rating profile load by tag 2013-06-28 22:47:39 +03:00
Radu Ioan Fericean
15e5709e29 started cdrexporter and fallback list by ; not | 2013-06-26 18:33:46 +03:00
Radu Ioan Fericean
3380bf8492 fallback subject list with | 2013-06-26 17:56:15 +03:00
Radu Ioan Fericean
088911a5c8 added extra fields to cdrs 2013-06-26 17:15:15 +03:00
Radu Ioan Fericean
096818feb2 AddAccount API method 2013-06-19 18:35:32 +03:00
Radu Ioan Fericean
070df3fef0 typo fix 2013-06-18 12:32:08 +03:00
Radu Ioan Fericean
a2cae467fe updated docs 2013-06-18 12:28:44 +03:00
Radu Ioan Fericean
61ec52c951 fixses for get/set destinations 2013-06-18 12:12:36 +03:00
Radu Ioan Fericean
609e1213c3 added AddTriggeredAction 2013-06-18 11:18:54 +03:00
Radu Ioan Fericean
d48bd013b7 added tennant and other fields that compose account names 2013-06-17 18:02:10 +03:00
Radu Ioan Fericean
ee25f00d85 docs fixes 2013-06-17 16:34:54 +03:00
Radu Ioan Fericean
e1636b3973 small fix 2013-06-17 16:32:47 +03:00
Radu Ioan Fericean
c9885fad86 added API for destinations 2013-06-17 16:31:15 +03:00
Radu Ioan Fericean
139bab220c started API documentation 2013-06-17 08:49:24 +03:00
Radu Ioan Fericean
ba2665a9a8 more API methods, docs pending 2013-06-16 23:26:40 +03:00
Radu Ioan Fericean
74b7836937 working on apier 2013-06-16 08:12:26 +03:00
Radu Ioan Fericean
3fcf811eab started to add apier methods 2013-06-14 20:40:09 +03:00
Radu Ioan Fericean
52616d09e6 loading from database code complete, needs testing 2013-06-14 15:39:02 +03:00
Radu Ioan Fericean
c8f61690b4 left only one redis storage implementation 2013-06-14 12:59:22 +03:00
DanB
8417f8c581 Removing tpid api since we do not longer need it 2013-06-12 13:22:32 +02:00
DanB
ee7bd686bd Adding NOT_IMPLEMENTED error at storage level 2013-06-12 12:33:06 +02:00
DanB
a254242b46 Adding GetTPDestinations API and storage method 2013-06-12 12:27:30 +02:00
DanB
dd14b7e210 Modifying int into float for tp_tables 2013-06-11 16:21:33 +02:00
DanB
0fbd35627a Moving GenUUID into utils 2013-06-11 16:09:35 +02:00
DanB
198a53b7ed Cleanup automatically added swap files 2013-06-11 13:35:28 +02:00
DanB
e703c3f431 Adding Apier, Apier.GetTPID initial implementation 2013-06-11 13:25:57 +02:00
Radu Ioan Fericean
d4130df421 more work on destinations 2013-06-10 11:15:31 +03:00
Radu Ioan Fericean
76001c74db better API docs links 2013-06-10 10:44:54 +03:00
Radu Ioan Fericean
a6ce08d8b4 started importing and management API 2013-06-10 10:38:24 +03:00
Radu Ioan Fericean
7c811469ab fixed build error 2013-06-08 20:12:20 +03:00
Radu Ioan Fericean
aca4c5a861 first draft of DataStorage loading 2013-06-08 19:12:24 +03:00
Radu Ioan Fericean
7c57d3a321 moved loader to datastorage interface all the way to rating profiles 2013-06-08 01:33:58 +03:00
Radu Ioan Fericean
8f692b5c9e fixed loading issue 2013-06-06 16:43:33 +03:00
Radu Ioan Fericean
b8ab0c9657 reverted to my marshaller 2013-06-06 16:00:52 +03:00
DanB
dd096da7e6 Adding mediator/loadConfig mechanism description 2013-06-06 13:55:24 +02:00
DanB
a75c2e7324 Mediator refactoring to support CDR as imput instead of csv row - adding separation of names and indexes, StartTime->AnswerTime, adding reqtype=rated 2013-06-06 12:37:36 +02:00
Radu Ioan Fericean
fbbb26fab3 Merge branch 'master' of github.com:cgrates/cgrates 2013-06-03 10:42:57 +03:00
DanB
81e2c8bd4e Initial modifications towards a new Mediator structure. Tests and compilation will fail until Mediator fixed 2013-06-02 15:07:03 +02:00
DanB
8ac8eae692 Modifying mediator configuration to match the new architecture required to work with both file and database cdrs 2013-06-02 15:06:16 +02:00
Radu Ioan Fericean
dca4cd0038 added db loader 2013-06-01 16:45:37 +03:00
DanB
3d1656efdb Modifying config file with new mediation fields 2013-05-31 17:18:46 +02:00
Radu Ioan Fericean
ea15c17832 added binc marshaller and added actions 2013-05-31 17:02:21 +03:00
Radu Ioan Fericean
84f489a73a small fixses 2013-05-30 18:26:27 +03:00
DanB
6966a85e68 Refactoring transactions into costdetails table, due to internal concepts 2013-05-30 13:29:28 +02:00
DanB
c06b1e3c59 Adding renaming callcosts into transactions table 2013-05-30 13:00:24 +02:00
Radu Ioan Fericean
92ccdc7fe5 addes store for freeswitch cdr 2013-05-30 13:03:19 +03:00
Radu Ioan Fericean
fed39c6fdd simplified my marshaller 2013-05-30 12:49:06 +03:00
Radu Ioan Fericean
c70d54d790 Merge branch 'master' into marsh 2013-05-30 10:20:03 +03:00
Radu Ioan Fericean
4e7dc77db9 get cost from call cost 2013-05-29 19:21:24 +03:00
Radu Ioan Fericean
dacaa14086 removed rate 2013-05-29 19:19:21 +03:00
Radu Ioan Fericean
bd74e573b9 added cost to rated cdrs 2013-05-29 19:16:39 +03:00
DanB
72e054a9b3 Adding tariffplan tables 2013-05-29 15:05:28 +02:00
DanB
6875f11c35 Replacing GetEndTime with GetDuration, support for MySQL as LogDB 2013-05-28 16:30:29 +02:00
DanB
ef6b1cc9f8 Adding some more tables on mysql side 2013-05-28 16:29:33 +02:00
DanB
f0e82ba4af Refactoring startCDRS to be in trend with the rest of start functions 2013-05-28 14:38:23 +02:00
DanB
a2afe0794a Fixup timestamps source in cdrs, modified cmd/cgr-rater to support disabling mediator as service in configuration 2013-05-28 14:06:08 +02:00
DanB
af97063031 Adding missing values out of fscdr 2013-05-27 17:49:35 +02:00
Radu Ioan Fericean
cd8509084b cdrs before testing 2013-05-26 21:16:00 +03:00
Radu Ioan Fericean
6345b78a44 cdr server capturing events 2013-05-26 18:24:05 +03:00
Radu Ioan Fericean
ba6fd4ae83 started 2013-05-24 10:37:40 +03:00
Radu Ioan Fericean
1c943ff6a3 reverted to standard things 2013-05-23 16:56:11 +03:00
Radu Ioan Fericean
38d951437f mediator parse only non-csv files 2013-05-23 16:52:44 +03:00
Radu Ioan Fericean
84720809ba syslog logging 2013-05-23 15:52:18 +03:00
Radu Ioan Fericean
e40f4349a8 replace inotify with fsnotify 2013-05-23 15:36:53 +03:00
DanB
eaf432e3ad Merge branch 'master' of https://github.com/cgrates/cgrates 2013-05-23 14:23:59 +02:00
DanB
147aef6dc5 Adding mediation tables, modified cdrs_primary to include source host of the CDR 2013-05-23 14:23:52 +02:00
DanB
e3154f5824 Adding new parameters to config, improved testing of config 2013-05-23 13:43:33 +02:00
Radu Ioan Fericean
12d7312853 travis is back 2013-05-23 12:42:32 +03:00
Radu Ioan Fericean
a68fc52891 please travis 2013-05-23 12:39:14 +03:00
Radu Ioan Fericean
d8194b95cf travis last try 2013-05-23 12:37:40 +03:00
Radu Ioan Fericean
0e856b7d80 switched to drone.io 2013-05-23 12:35:35 +03:00
Radu Ioan Fericean
d1af53fb1b intentional test fail to test ci 2013-05-23 12:29:26 +03:00
Radu Ioan Fericean
ef8e2f32b3 more travis 2013-05-23 12:10:24 +03:00
Radu Ioan Fericean
913f28a5f6 test travis 2013-05-23 12:05:38 +03:00
Radu Ioan Fericean
62511ee9e3 removed name from cdr and it should compile on go1.0.x now 2013-05-23 11:36:09 +03:00
DanB
83ef5b6d6f Adding CDRS configuration, better handling and testing of the defaults 2013-05-22 12:32:30 +02:00
DanB
08184114ab Renaming internal variables for json_cdr 2013-05-21 17:04:24 +02:00
Radu Ioan Fericean
fe1790b1f7 fixed test compilation error 2013-05-21 15:09:23 +03:00
Radu Ioan Fericean
bcd6d20197 Merge branch 'cdrs' 2013-05-21 15:03:44 +03:00
DanB
52376fadd0 Adding FreeSWITCH tutorial to documentation 2013-05-21 13:27:55 +02:00
DanB
6d28da7d64 Removing unused configs 2013-05-21 12:40:05 +02:00
DanB
afac0bdfc7 Fixup config files 2013-05-21 12:38:23 +02:00
Radu Ioan Fericean
aa3e3941ed added cdr server to cdr-rater tool 2013-05-19 20:27:25 +03:00
Radu Ioan Fericean
d7f666e323 more refactorings and cdr test 2013-05-19 20:06:08 +03:00
Radu Ioan Fericean
cdfea5aba4 code reorganization and added cdr interface 2013-05-19 15:57:09 +03:00
Radu Ioan Fericean
faaca5ae57 simplifyed fsevent 2013-05-19 14:47:13 +03:00
356 changed files with 37495 additions and 10604 deletions

3
.gitignore vendored
View File

@@ -9,3 +9,6 @@ a.out
docs/_*
bin
.idea
dean*
data/vagrant/.vagrant
data/vagrant/vagrant_ansible_inventory_default

View File

@@ -1,6 +1,9 @@
language: go
before_install: sudo apt-get -q install bzr
go:
- 1.2
script: $TRAVIS_BUILD_DIR/test.sh
branches:
only: master
@@ -12,8 +15,6 @@ notifications:
on_success: change
on_failure: always
email:
on_success: change
on_success: never
on_failure: always
after_script:
./build.sh && ./test.sh

View File

@@ -1,6 +1,8 @@
# Rating system for Telecom & ISP environments #
## Rating system for Telecom & ISP environments ##
## Features ##
[![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)
### Features ###
+ Rates for prepaid and for postpaid
+ The budget expressed in money and/or minutes (seconds)
+ High accuracy rating: configurable to milliseconds
@@ -11,15 +13,15 @@
+ Good documentation
+ Commercial support available
## Documentation ##
Install & run screencast: http://youtu.be/qTQZZpb-m7Q
### Documentation ###
[Step by steps tutorials](https://cgrates.readthedocs.org/en/latest/tut_freeswitch.html)
Browsable HTML http://readthedocs.org/docs/cgrates/
[Debian apt-get repository](https://cgrates.readthedocs.org/en/latest/tut_freeswitch_installs.html#cgrates)
Browsable HTML docs http://readthedocs.org/docs/cgrates/
PDF, Epub, Manpage http://readthedocs.org/projects/cgrates/downloads/
API reference http://gopkgdoc.appspot.com/pkg/github.com/cgrates/cgrates
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.
Continous integration: [![Build Status](https://goci.herokuapp.com/project/image/github.com/cgrates/cgrates "Continous integration")](http://goci.me/project/github.com/cgrates/cgrates) [![Build Status](https://secure.travis-ci.org/cgrates/cgrates.png)](http://travis-ci.org/cgrates/cgrates)

221
apier/accounts.go Normal file
View File

@@ -0,0 +1,221 @@
/*
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 apier
import (
"errors"
"fmt"
"time"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
type AttrAcntAction struct {
Tenant string
Account string
Direction string
}
type AccountActionTiming struct {
ActionPlanId string // The id of the ActionPlanId profile attached to the account
Uuid string // The id to reference this particular ActionTiming
ActionsId string // The id of actions which will be executed
NextExecTime time.Time // Next execution time
}
func (self *ApierV1) GetAccountActionPlan(attrs AttrAcntAction, reply *[]*AccountActionTiming) error {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
accountATs := make([]*AccountActionTiming, 0)
allATs, err := self.AccountDb.GetAllActionTimings()
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
for _, ats := range allATs {
for _, at := range ats {
if utils.IsSliceMember(at.AccountIds, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)) {
accountATs = append(accountATs, &AccountActionTiming{Uuid: at.Uuid, ActionPlanId: at.Id, ActionsId: at.ActionsId, NextExecTime: at.GetNextStartTime(time.Now())})
}
}
}
*reply = accountATs
return nil
}
type AttrRemActionTiming struct {
ActionPlanId string // Id identifying the ActionTimings profile
ActionTimingId string // Internal CGR id identifying particular ActionTiming, *all for all user related ActionTimings to be canceled
Tenant string // Tenant he account belongs to
Account string // Account name
Direction string // Traffic direction
ReloadScheduler bool // If set it will reload the scheduler after adding
}
// Removes an ActionTimings or parts of it depending on filters being set
func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"ActionPlanId"}); len(missing) != 0 { // Only mandatory ActionPlanId
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if len(attrs.Account) != 0 { // Presence of Account requires complete account details to be provided
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
}
_, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) {
ats, err := self.AccountDb.GetActionTimings(attrs.ActionPlanId)
if err != nil {
return 0, err
} 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))
if err := self.AccountDb.SetActionTimings(attrs.ActionPlanId, ats); err != nil {
return 0, err
}
return 0, nil
})
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if attrs.ReloadScheduler && self.Sched != nil {
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
}
*reply = OK
return nil
}
// Returns a list of ActionTriggers on an account
func (self *ApierV1) GetAccountActionTriggers(attrs AttrAcntAction, reply *engine.ActionTriggerPriotityList) error {
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 {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = balance.ActionTriggers
}
return nil
}
type AttrRemAcntActionTriggers struct {
Tenant string // Tenant he account belongs to
Account string // Account name
Direction string // Traffic direction
ActionTriggerId string // Id filtering only specific id to remove
}
// Returns a list of ActionTriggers on an account
func (self *ApierV1) RemAccountActionTriggers(attrs AttrRemAcntActionTriggers, reply *string) error {
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)
_, err := engine.AccLock.Guard(balanceId, func() (float64, error) {
ub, err := self.AccountDb.GetAccount(balanceId)
if err != nil {
return 0, err
}
for idx, actr := range ub.ActionTriggers {
if len(attrs.ActionTriggerId) != 0 && actr.Id != attrs.ActionTriggerId { // Empty actionTriggerId will match always
continue
}
if len(ub.ActionTriggers) != 1 { // Remove by index
ub.ActionTriggers[idx], ub.ActionTriggers = ub.ActionTriggers[len(ub.ActionTriggers)-1], ub.ActionTriggers[:len(ub.ActionTriggers)-1]
} else { // For last item, simply reinit the slice
ub.ActionTriggers = make(engine.ActionTriggerPriotityList, 0)
}
}
if err := self.AccountDb.SetAccount(ub); err != nil {
return 0, err
}
return 0, nil
})
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = OK
return nil
}
type AttrSetAccount struct {
Tenant string
Direction string
Account string
ActionPlanId string
AllowNegative bool
}
// Ads a new account into dataDb. If already defined, returns success.
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)
var ub *engine.Account
var ats engine.ActionPlan
_, err := engine.AccLock.Guard(balanceId, func() (float64, error) {
if bal, _ := self.AccountDb.GetAccount(balanceId); bal != nil {
ub = bal
} else { // Not found in db, create it here
ub = &engine.Account{
Id: balanceId,
AllowNegative: attr.AllowNegative,
}
}
if len(attr.ActionPlanId) != 0 {
var err error
ats, err = self.AccountDb.GetActionTimings(attr.ActionPlanId)
if err != nil {
return 0, err
}
for _, at := range ats {
at.AccountIds = append(at.AccountIds, balanceId)
}
}
// All prepared, save account
if err := self.AccountDb.SetAccount(ub); err != nil {
return 0, err
}
return 0, nil
})
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if len(ats) != 0 {
_, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { // ToDo: Try locking it above on read somehow
if err := self.AccountDb.SetActionTimings(attr.ActionPlanId, ats); err != nil {
return 0, err
}
return 0, nil
})
if err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if self.Sched != nil {
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
}
}
*reply = OK // This will mark saving of the account, error still can show up in actionTimingsId
return nil
}

648
apier/apier.go Normal file
View File

@@ -0,0 +1,648 @@
/*
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 apier
import (
"errors"
"fmt"
"path"
"time"
"github.com/cgrates/cgrates/cache2go"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/scheduler"
"github.com/cgrates/cgrates/utils"
)
const (
OK = "OK"
)
type ApierV1 struct {
StorDb engine.LoadStorage
RatingDb engine.RatingStorage
AccountDb engine.AccountingStorage
CdrDb engine.CdrStorage
LogDb engine.LogStorage
Sched *scheduler.Scheduler
Config *config.CGRConfig
}
func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) error {
if dst, err := self.RatingDb.GetDestination(dstId); err != nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = *dst
}
return nil
}
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)
} else {
*reply = *rpln
}
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 {
tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account)
userBalance, err := self.AccountDb.GetAccount(tag)
if err != nil {
return err
}
*reply = *userBalance
return nil
}
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
}
func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
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
ub := &engine.Account{
Id: tag,
}
if err := self.AccountDb.SetAccount(ub); err != nil {
*reply = err.Error()
return err
}
}
at := &engine.ActionTiming{
AccountIds: []string{tag},
}
if attr.Direction == "" {
attr.Direction = engine.OUTBOUND
}
aType := engine.TOPUP
if attr.Overwrite {
aType = engine.TOPUP_RESET
}
at.SetActions(engine.Actions{
&engine.Action{
ActionType: aType,
BalanceType: attr.BalanceType,
Direction: attr.Direction,
Balance: &engine.Balance{
Value: attr.Value,
ExpirationDate: attr.ExpirationDate,
RateSubject: attr.RatingSubject,
DestinationId: attr.DestinationId,
Weight: attr.Weight,
},
},
})
if err := at.Execute(); err != nil {
*reply = err.Error()
return err
}
*reply = OK
return nil
}
type AttrExecuteAction struct {
Direction string
Tenant string
Account string
ActionsId string
}
func (self *ApierV1) ExecuteAction(attr *AttrExecuteAction, reply *string) error {
tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account)
at := &engine.ActionTiming{
AccountIds: []string{tag},
ActionsId: attr.ActionsId,
}
if err := at.Execute(); err != nil {
*reply = err.Error()
return err
}
*reply = OK
return nil
}
type AttrLoadRatingPlan struct {
TPid string
RatingPlanId string
}
// Process dependencies and load a specific rating plan from storDb into dataDb.
func (self *ApierV1) LoadRatingPlan(attrs AttrLoadRatingPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
dbReader := engine.NewDbReader(self.StorDb, self.RatingDb, self.AccountDb, attrs.TPid)
if loaded, err := dbReader.LoadRatingPlanByTag(attrs.RatingPlanId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if !loaded {
return errors.New("NOT_FOUND")
}
//Automatic cache of the newly inserted rating plan
didNotChange := []string{}
if err := self.RatingDb.CacheRating(nil, nil, didNotChange, didNotChange); err != nil {
return err
}
*reply = OK
return nil
}
// Process dependencies and load a specific rating profile from storDb into dataDb.
func (self *ApierV1) LoadRatingProfile(attrs utils.TPRatingProfile, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "TOR", "Direction", "Subject"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
dbReader := engine.NewDbReader(self.StorDb, self.RatingDb, self.AccountDb, attrs.TPid)
if err := dbReader.LoadRatingProfileFiltered(&attrs); 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 + attrs.KeyId()}, didNotChange); err != nil {
return err
}
*reply = OK
return nil
}
type AttrSetRatingProfile struct {
Tenant string // Tenant's Id
TOR 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
RatingPlanActivations []*utils.TPRatingActivation // Activate rating plans at specific time
}
// Sets a specific rating profile working with data directly in the RatingDb without involving storDb
func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "TOR", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
for _, rpa := range attrs.RatingPlanActivations {
if missing := utils.MissingStructFields(rpa, []string{"ActivationTime", "RatingPlanId"}); len(missing) != 0 {
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}
keyId := tpRpf.KeyId()
if !attrs.Overwrite {
if exists, err := self.RatingDb.HasData(engine.RATING_PROFILE_PREFIX, keyId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if exists {
return errors.New(utils.ERR_EXISTS)
}
}
rpfl := &engine.RatingProfile{Id: keyId, RatingPlanActivations: make(engine.RatingPlanActivations, len(attrs.RatingPlanActivations))}
for idx, ra := range attrs.RatingPlanActivations {
at, err := utils.ParseDate(ra.ActivationTime)
if err != nil {
return fmt.Errorf(fmt.Sprintf("%s:Cannot parse activation time from %v", utils.ERR_SERVER_ERROR, ra.ActivationTime))
}
if exists, err := self.RatingDb.HasData(engine.RATING_PLAN_PREFIX, ra.RatingPlanId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if !exists {
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)}
}
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 {
return err
}
*reply = OK
return nil
}
type AttrSetActions struct {
ActionsId string // Actions id
Overwrite bool // If previously defined, will be overwritten
Actions []*utils.TPAction // Set of actions this Actions profile will perform
}
func (self *ApierV1) SetActions(attrs AttrSetActions, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"ActionsId", "Actions"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
for _, action := range attrs.Actions {
requiredFields := []string{"Identifier", "Weight"}
if action.BalanceType != "" { // Add some inter-dependent parameters - if balanceType then we are not talking about simply calling actions
requiredFields = append(requiredFields, "Direction", "Units")
}
if missing := utils.MissingStructFields(action, requiredFields); len(missing) != 0 {
return fmt.Errorf("%s:Action:%s:%v", utils.ERR_MANDATORY_IE_MISSING, action.Identifier, missing)
}
}
if !attrs.Overwrite {
if exists, err := self.AccountDb.HasData(engine.ACTION_PREFIX, attrs.ActionsId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if exists {
return errors.New(utils.ERR_EXISTS)
}
}
storeActions := make(engine.Actions, len(attrs.Actions))
for idx, apiAct := range attrs.Actions {
a := &engine.Action{
Id: utils.GenUUID(),
ActionType: apiAct.Identifier,
BalanceType: apiAct.BalanceType,
Direction: apiAct.Direction,
Weight: apiAct.Weight,
ExpirationString: apiAct.ExpiryTime,
ExtraParameters: apiAct.ExtraParameters,
Balance: &engine.Balance{
Uuid: utils.GenUUID(),
Value: apiAct.Units,
Weight: apiAct.BalanceWeight,
DestinationId: apiAct.DestinationId,
RateSubject: apiAct.RatingSubject,
},
}
storeActions[idx] = a
}
if err := self.AccountDb.SetActions(attrs.ActionsId, storeActions); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = OK
return nil
}
type AttrSetActionPlan struct {
Id string // Profile id
ActionPlan []*ApiActionTiming // Set of actions this Actions profile will perform
Overwrite bool // If previously defined, will be overwritten
ReloadScheduler bool // Enables automatic reload of the scheduler (eg: useful when adding a single action timing)
}
type ApiActionTiming struct {
ActionsId string // Actions id
Years string // semicolon separated list of years this timing is valid on, *any or empty supported
Months string // semicolon separated list of months this timing is valid on, *any or empty supported
MonthDays string // semicolon separated list of month's days this timing is valid on, *any or empty supported
WeekDays string // semicolon separated list of week day names this timing is valid on *any or empty supported
Time string // String representing the time this timing starts on, *asap supported
Weight float64 // Binding's weight
}
func (self *ApierV1) SetActionPlan(attrs AttrSetActionPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"Id", "ActionPlan"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
for _, at := range attrs.ActionPlan {
requiredFields := []string{"ActionsId", "Time", "Weight"}
if missing := utils.MissingStructFields(at, requiredFields); len(missing) != 0 {
return fmt.Errorf("%s:Action:%s:%v", utils.ERR_MANDATORY_IE_MISSING, at.ActionsId, missing)
}
}
if !attrs.Overwrite {
if exists, err := self.AccountDb.HasData(engine.ACTION_TIMING_PREFIX, attrs.Id); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if exists {
return errors.New(utils.ERR_EXISTS)
}
}
storeAtms := make(engine.ActionPlan, len(attrs.ActionPlan))
for idx, apiAtm := range attrs.ActionPlan {
if exists, err := self.AccountDb.HasData(engine.ACTION_PREFIX, apiAtm.ActionsId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if !exists {
return fmt.Errorf("%s:%s", utils.ERR_BROKEN_REFERENCE, err.Error())
}
timing := new(engine.RITiming)
timing.Years.Parse(apiAtm.Years, ";")
timing.Months.Parse(apiAtm.Months, ";")
timing.MonthDays.Parse(apiAtm.MonthDays, ";")
timing.WeekDays.Parse(apiAtm.WeekDays, ";")
timing.StartTime = apiAtm.Time
at := &engine.ActionTiming{
Uuid: utils.GenUUID(),
Id: attrs.Id,
Weight: apiAtm.Weight,
Timing: &engine.RateInterval{Timing: timing},
ActionsId: apiAtm.ActionsId,
}
storeAtms[idx] = at
}
if err := self.AccountDb.SetActionTimings(attrs.Id, storeAtms); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if attrs.ReloadScheduler {
if self.Sched == nil {
return errors.New("SCHEDULER_NOT_ENABLED")
}
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
}
*reply = OK
return nil
}
type AttrAddActionTrigger struct {
Tenant string
Account string
Direction string
BalanceType string
ThresholdType string
ThresholdValue float64
DestinationId string
Weight float64
ActionsId string
}
func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error {
if attr.Direction == "" {
attr.Direction = engine.OUTBOUND
}
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,
}
tag := utils.BalanceKey(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
}
userBalance.ActionTriggers = append(userBalance.ActionTriggers, at)
if err = self.AccountDb.SetAccount(userBalance); 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 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
dbReader := engine.NewDbReader(self.StorDb, self.RatingDb, self.AccountDb, attrs.TPid)
if _, err := engine.AccLock.Guard(attrs.KeyId(), func() (float64, error) {
if err := dbReader.LoadAccountActionsFiltered(&attrs); err != nil {
return 0, err
}
return 0, nil
}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
// 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 {
return err
}
if self.Sched != nil {
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
}
*reply = OK
return nil
}
func (self *ApierV1) ReloadScheduler(input string, reply *string) error {
if self.Sched == nil {
return errors.New(utils.ERR_NOT_FOUND)
}
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
*reply = OK
return nil
}
func (self *ApierV1) ReloadCache(attrs utils.ApiReloadCache, reply *string) error {
var dstKeys, rpKeys, rpfKeys, actKeys, shgKeys, rpAlsKeys, accAlsKeys []string
if len(attrs.DestinationIds) > 0 {
dstKeys = make([]string, len(attrs.DestinationIds))
for idx, dId := range attrs.DestinationIds {
dstKeys[idx] = engine.DESTINATION_PREFIX + dId // Cache expects them as redis keys
}
}
if len(attrs.RatingPlanIds) > 0 {
rpKeys = make([]string, len(attrs.RatingPlanIds))
for idx, rpId := range attrs.RatingPlanIds {
rpKeys[idx] = engine.RATING_PLAN_PREFIX + rpId
}
}
if len(attrs.RatingProfileIds) > 0 {
rpfKeys = make([]string, len(attrs.RatingProfileIds))
for idx, rpfId := range attrs.RatingProfileIds {
rpfKeys[idx] = engine.RATING_PROFILE_PREFIX + rpfId
}
}
if len(attrs.ActionIds) > 0 {
actKeys = make([]string, len(attrs.ActionIds))
for idx, actId := range attrs.ActionIds {
actKeys[idx] = engine.ACTION_PREFIX + actId
}
}
if len(attrs.SharedGroupIds) > 0 {
shgKeys = make([]string, len(attrs.SharedGroupIds))
for idx, shgId := range attrs.SharedGroupIds {
shgKeys[idx] = engine.SHARED_GROUP_PREFIX + shgId
}
}
if len(attrs.RpAliases) > 0 {
rpAlsKeys = make([]string, len(attrs.RpAliases))
for idx, alias := range attrs.RpAliases {
rpAlsKeys[idx] = engine.RP_ALIAS_PREFIX + alias
}
}
if len(attrs.AccAliases) > 0 {
accAlsKeys = make([]string, len(attrs.AccAliases))
for idx, alias := range attrs.AccAliases {
accAlsKeys[idx] = engine.ACC_ALIAS_PREFIX + alias
}
}
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys); err != nil {
return err
}
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys); err != nil {
return err
}
*reply = "OK"
return nil
}
func (self *ApierV1) GetCacheStats(attrs utils.AttrCacheStats, reply *utils.CacheStats) error {
cs := new(utils.CacheStats)
cs.Destinations = cache2go.CountEntries(engine.DESTINATION_PREFIX)
cs.RatingPlans = cache2go.CountEntries(engine.RATING_PLAN_PREFIX)
cs.RatingProfiles = cache2go.CountEntries(engine.RATING_PROFILE_PREFIX)
cs.Actions = cache2go.CountEntries(engine.ACTION_PREFIX)
cs.SharedGroups = cache2go.CountEntries(engine.SHARED_GROUP_PREFIX)
cs.RatingAliases = cache2go.CountEntries(engine.RP_ALIAS_PREFIX)
cs.AccountAliases = cache2go.CountEntries(engine.ACC_ALIAS_PREFIX)
*reply = *cs
return nil
}
func (self *ApierV1) GetCachedItemAge(itemId string, reply *utils.CachedItemAge) error {
if len(itemId) == 0 {
return fmt.Errorf("%s:ItemId", utils.ERR_MANDATORY_IE_MISSING)
}
cachedItemAge := new(utils.CachedItemAge)
var found bool
for idx, cacheKey := range []string{engine.DESTINATION_PREFIX + itemId, engine.RATING_PLAN_PREFIX + itemId, engine.RATING_PROFILE_PREFIX + itemId,
engine.ACTION_PREFIX + itemId, engine.SHARED_GROUP_PREFIX + itemId, engine.RP_ALIAS_PREFIX + itemId, engine.ACC_ALIAS_PREFIX + itemId} {
if age, err := cache2go.GetKeyAge(cacheKey); err == nil {
found = true
switch idx {
case 0:
cachedItemAge.Destination = age
case 1:
cachedItemAge.RatingPlan = age
case 2:
cachedItemAge.RatingProfile = age
case 3:
cachedItemAge.Action = age
case 4:
cachedItemAge.SharedGroup = age
case 5:
cachedItemAge.RatingAlias = age
case 6:
cachedItemAge.AccountAlias = age
}
}
}
if !found {
return errors.New(utils.ERR_NOT_FOUND)
}
*reply = *cachedItemAge
return nil
}
func (self *ApierV1) LoadTariffPlanFromFolder(attrs utils.AttrLoadTpFromFolder, reply *string) error {
loader := engine.NewFileCSVReader(self.RatingDb, self.AccountDb, utils.CSV_SEP,
path.Join(attrs.FolderPath, utils.DESTINATIONS_CSV),
path.Join(attrs.FolderPath, utils.TIMINGS_CSV),
path.Join(attrs.FolderPath, utils.RATES_CSV),
path.Join(attrs.FolderPath, utils.DESTINATION_RATES_CSV),
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.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))
if err := loader.LoadAll(); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
if attrs.DryRun {
*reply = "OK"
return nil // Mission complete, no errors
}
if err := loader.WriteToDatabase(attrs.FlushDb, false); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
// Make sure the items are in the cache
dstIds, _ := loader.GetLoadedIds(engine.DESTINATION_PREFIX)
dstKeys := make([]string, len(dstIds))
for idx, dId := range dstIds {
dstKeys[idx] = engine.DESTINATION_PREFIX + dId // Cache expects them as redis keys
}
rplIds, _ := loader.GetLoadedIds(engine.RATING_PLAN_PREFIX)
rpKeys := make([]string, len(rplIds))
for idx, rpId := range rplIds {
rpKeys[idx] = engine.RATING_PLAN_PREFIX + rpId
}
rpfIds, _ := loader.GetLoadedIds(engine.RATING_PROFILE_PREFIX)
rpfKeys := make([]string, len(rpfIds))
for idx, rpfId := range rpfIds {
rpfKeys[idx] = engine.RATING_PROFILE_PREFIX + rpfId
}
actIds, _ := loader.GetLoadedIds(engine.ACTION_PREFIX)
actKeys := make([]string, len(actIds))
for idx, actId := range actIds {
actKeys[idx] = engine.ACTION_PREFIX + actId
}
shgIds, _ := loader.GetLoadedIds(engine.SHARED_GROUP_PREFIX)
shgKeys := make([]string, len(shgIds))
for idx, shgId := range shgIds {
shgKeys[idx] = engine.SHARED_GROUP_PREFIX + shgId
}
rpAliases, _ := loader.GetLoadedIds(engine.RP_ALIAS_PREFIX)
rpAlsKeys := make([]string, len(rpAliases))
for idx, alias := range rpAliases {
rpAlsKeys[idx] = engine.RP_ALIAS_PREFIX + alias
}
accAliases, _ := loader.GetLoadedIds(engine.ACC_ALIAS_PREFIX)
accAlsKeys := make([]string, len(accAliases))
for idx, alias := range accAliases {
accAlsKeys[idx] = engine.ACC_ALIAS_PREFIX + alias
}
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys); err != nil {
return err
}
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys); err != nil {
return err
}
if self.Sched != nil {
self.Sched.LoadActionTimings(self.AccountDb)
self.Sched.Restart()
}
*reply = "OK"
return nil
}

1375
apier/apier_local_test.go Normal file

File diff suppressed because it is too large Load Diff

153
apier/cdre.go Normal file
View File

@@ -0,0 +1,153 @@
/*
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 apier
import (
"fmt"
"github.com/cgrates/cgrates/cdre"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"os"
"path"
"strconv"
"strings"
"time"
)
// Export Cdrs to file
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")
}
if len(attr.TimeStart) != 0 {
if tStart, err = utils.ParseTimeDetectLayout(attr.TimeStart); err != nil {
return err
}
}
if len(attr.TimeEnd) != 0 {
if tEnd, err = utils.ParseTimeDetectLayout(attr.TimeEnd); err != nil {
return err
}
}
fileName := attr.ExportFileName
exportId := attr.ExportId
if len(exportId) == 0 {
exportId = strconv.FormatInt(time.Now().Unix(), 10)
}
roundDecimals := attr.RoundingDecimals
if roundDecimals == 0 {
roundDecimals = self.Config.RoundingDecimals
}
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)
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}
}
return nil
}
// Remove Cdrs out of CDR storage
func (self *ApierV1) RemCdrs(attrs utils.AttrRemCdrs, reply *string) error {
if len(attrs.CgrIds) == 0 {
return fmt.Errorf("%s:CgrIds", utils.ERR_MANDATORY_IE_MISSING)
}
if err := self.CdrDb.RemStoredCdrs(attrs.CgrIds); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}

45
apier/cdrs.go Normal file
View File

@@ -0,0 +1,45 @@
/*
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 apier
import (
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
type AttrGetCallCost struct {
CgrId string // Unique id of the CDR
RunId string // Run Id
}
// Retrieves the callCost out of CGR logDb
func (apier *ApierV1) GetCallCostLog(attrs AttrGetCallCost, reply *engine.CallCost) error {
if missing := utils.MissingStructFields(&attrs, []string{"CgrId", "RunId"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if cc, err := apier.LogDb.GetCallCostLog(attrs.CgrId, "", attrs.RunId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if cc == nil {
return fmt.Errorf("NOT_FOUND")
} else {
*reply = *cc
}
return nil
}

42
apier/tp.go Normal file
View File

@@ -0,0 +1,42 @@
/*
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 apier
// Tariff plan related APIs
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
type AttrGetTPIds struct {
}
// Queries tarrif plan identities gathered from all tables.
func (self *ApierV1) GetTPIds(attrs AttrGetTPIds, reply *[]string) error {
if ids, err := self.StorDb.GetTPIds(); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}

104
apier/tpaccountactions.go Normal file
View File

@@ -0,0 +1,104 @@
/*
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 apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new AccountActions profile within a tariff plan
func (self *ApierV1) SetTPAccountActions(attrs utils.TPAccountActions, reply *string) error {
if missing := utils.MissingStructFields(&attrs,
[]string{"TPid", "LoadId", "Tenant", "Account", "Direction", "ActionPlanId", "ActionTriggersId"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.SetTPAccountActions(attrs.TPid, map[string]*utils.TPAccountActions{attrs.KeyId(): &attrs}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPAccountActions struct {
TPid string // Tariff plan id
LoadId string // AccountActions id
}
// Queries specific AccountActions profile on tariff plan
func (self *ApierV1) GetTPAccountActions(attrs utils.TPAccountActions, reply *[]*utils.TPAccountActions) error {
mndtryFlds := []string{"TPid", "LoadId"}
if len(attrs.Account) != 0 { // If account provided as filter, make all related fields mandatory
mndtryFlds = append(mndtryFlds, "Tenant", "Account", "Direction")
}
if missing := utils.MissingStructFields(&attrs, mndtryFlds); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if aa, err := self.StorDb.GetTpAccountActions(&attrs); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(aa) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
var acts []*utils.TPAccountActions
if len(attrs.Account) != 0 {
acts = []*utils.TPAccountActions{aa[attrs.KeyId()]}
} else {
for _, actLst := range aa {
acts = append(acts, actLst)
}
}
*reply = acts
}
return nil
}
type AttrGetTPAccountActionIds struct {
TPid string // Tariff plan id
}
// Queries AccountActions identities on specific tariff plan.
func (self *ApierV1) GetTPAccountActionLoadIds(attrs AttrGetTPAccountActionIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_ACCOUNT_ACTIONS, "loadid", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific AccountActions on Tariff plan
func (self *ApierV1) RemTPAccountActions(attrs utils.TPAccountActions, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Account", "Direction"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_ACCOUNT_ACTIONS, attrs.TPid, attrs.LoadId, attrs.Tenant, attrs.Account, attrs.Direction); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

99
apier/tpactions.go Normal file
View File

@@ -0,0 +1,99 @@
/*
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 apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new Actions profile within a tariff plan
func (self *ApierV1) SetTPActions(attrs utils.TPActions, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionsId", "Actions"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
for _, action := range attrs.Actions {
requiredFields := []string{"Identifier", "Weight"}
if action.BalanceType != "" { // Add some inter-dependent parameters - if balanceType then we are not talking about simply calling actions
requiredFields = append(requiredFields, "Direction", "Units")
}
if missing := utils.MissingStructFields(action, requiredFields); len(missing) != 0 {
return fmt.Errorf("%s:Action:%s:%v", utils.ERR_MANDATORY_IE_MISSING, action.Identifier, missing)
}
}
if err := self.StorDb.SetTPActions(attrs.TPid, map[string][]*utils.TPAction{attrs.ActionsId: attrs.Actions}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPActions struct {
TPid string // Tariff plan id
ActionsId string // Actions id
}
// Queries specific Actions profile on tariff plan
func (self *ApierV1) GetTPActions(attrs AttrGetTPActions, reply *utils.TPActions) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionsId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if acts, err := self.StorDb.GetTpActions(attrs.TPid, attrs.ActionsId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(acts) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = utils.TPActions{TPid: attrs.TPid, ActionsId: attrs.ActionsId, Actions: acts[attrs.ActionsId]}
}
return nil
}
type AttrGetTPActionIds struct {
TPid string // Tariff plan id
}
// Queries Actions identities on specific tariff plan.
func (self *ApierV1) GetTPActionIds(attrs AttrGetTPActionIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_ACTIONS, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific Actions on Tariff plan
func (self *ApierV1) RemTPActions(attrs AttrGetTPActions, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionsId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_ACTIONS, attrs.TPid, attrs.ActionsId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

97
apier/tpactiontimings.go Normal file
View File

@@ -0,0 +1,97 @@
/*
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 apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new ActionTimings profile within a tariff plan
func (self *ApierV1) SetTPActionPlan(attrs utils.TPActionPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "Id", "ActionPlan"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
for _, at := range attrs.ActionPlan {
requiredFields := []string{"ActionsId", "TimingId", "Weight"}
if missing := utils.MissingStructFields(at, requiredFields); len(missing) != 0 {
return fmt.Errorf("%s:Action:%s:%v", utils.ERR_MANDATORY_IE_MISSING, at.ActionsId, missing)
}
}
if err := self.StorDb.SetTPActionTimings(attrs.TPid, map[string][]*utils.TPActionTiming{attrs.Id: attrs.ActionPlan}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPActionPlan struct {
TPid string // Tariff plan id
Id string // ActionTimings id
}
// Queries specific ActionPlan profile on tariff plan
func (self *ApierV1) GetTPActionPlan(attrs AttrGetTPActionPlan, reply *utils.TPActionPlan) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "Id"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ats, err := self.StorDb.GetTPActionTimings(attrs.TPid, attrs.Id); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(ats) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else { // Got the data we need, convert it
atRply := &utils.TPActionPlan{attrs.TPid, attrs.Id, ats[attrs.Id]}
*reply = *atRply
}
return nil
}
type AttrGetTPActionPlanIds struct {
TPid string // Tariff plan id
}
// Queries ActionPlan identities on specific tariff plan.
func (self *ApierV1) GetTPActionPlanIds(attrs AttrGetTPActionPlanIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_ACTION_PLANS, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific ActionPlan on Tariff plan
func (self *ApierV1) RemTPActionPlan(attrs AttrGetTPActionPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "Id"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_ACTION_PLANS, attrs.TPid, attrs.Id); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

95
apier/tpactiontriggers.go Normal file
View File

@@ -0,0 +1,95 @@
/*
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 apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new ActionTriggers profile within a tariff plan
func (self *ApierV1) SetTPActionTriggers(attrs utils.TPActionTriggers, reply *string) error {
if missing := utils.MissingStructFields(&attrs,
[]string{"TPid", "ActionTriggersId"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
ats := map[string][]*utils.TPActionTrigger{
attrs.ActionTriggersId: attrs.ActionTriggers}
if err := self.StorDb.SetTPActionTriggers(attrs.TPid, ats); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPActionTriggers struct {
TPid string // Tariff plan id
ActionTriggersId string // ActionTrigger id
}
// Queries specific ActionTriggers profile on tariff plan
func (self *ApierV1) GetTPActionTriggers(attrs AttrGetTPActionTriggers, reply *utils.TPActionTriggers) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionTriggersId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if atsMap, err := self.StorDb.GetTpActionTriggers(attrs.TPid, attrs.ActionTriggersId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(atsMap) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
atRply := &utils.TPActionTriggers{attrs.TPid, attrs.ActionTriggersId, atsMap[attrs.ActionTriggersId]}
*reply = *atRply
}
return nil
}
type AttrGetTPActionTriggerIds struct {
TPid string // Tariff plan id
}
// Queries ActionTriggers identities on specific tariff plan.
func (self *ApierV1) GetTPActionTriggerIds(attrs AttrGetTPActionTriggerIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_ACTION_TRIGGERS, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific ActionTriggers on Tariff plan
func (self *ApierV1) RemTPActionTriggers(attrs AttrGetTPActionTriggers, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "ActionTriggersId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_ACTION_TRIGGERS, attrs.TPid, attrs.ActionTriggersId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

View File

@@ -0,0 +1,92 @@
/*
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 apier
// This file deals with tp_destination_rates management over APIs
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new DestinationRate profile within a tariff plan
func (self *ApierV1) SetTPDestinationRate(attrs utils.TPDestinationRate, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationRateId", "DestinationRates"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.SetTPDestinationRates(attrs.TPid, map[string][]*utils.DestinationRate{attrs.DestinationRateId: attrs.DestinationRates}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPDestinationRate struct {
TPid string // Tariff plan id
DestinationRateId string // Rate id
}
// Queries specific DestinationRate profile on tariff plan
func (self *ApierV1) GetTPDestinationRate(attrs AttrGetTPDestinationRate, reply *utils.TPDestinationRate) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationRateId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if drs, err := self.StorDb.GetTpDestinationRates(attrs.TPid, attrs.DestinationRateId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(drs) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = *drs[attrs.DestinationRateId]
}
return nil
}
type AttrTPDestinationRateIds struct {
TPid string // Tariff plan id
}
// Queries DestinationRate identities on specific tariff plan.
func (self *ApierV1) GetTPDestinationRateIds(attrs AttrGetTPRateIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_DESTINATION_RATES, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific DestinationRate on Tariff plan
func (self *ApierV1) RemTPDestinationRate(attrs AttrGetTPDestinationRate, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationRateId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_DESTINATION_RATES, attrs.TPid, attrs.DestinationRateId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

90
apier/tpdestinations.go Normal file
View File

@@ -0,0 +1,90 @@
/*
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 apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// Creates a new destination within a tariff plan
func (self *ApierV1) SetTPDestination(attrs utils.TPDestination, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationId", "Prefixes"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.SetTPDestination(attrs.TPid, &engine.Destination{Id: attrs.DestinationId, Prefixes: attrs.Prefixes}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPDestination struct {
TPid string // Tariff plan id
DestinationId string // Destination id
}
// Queries a specific destination
func (self *ApierV1) GetTPDestination(attrs AttrGetTPDestination, reply *utils.TPDestination) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if dsts, err := self.StorDb.GetTpDestinations(attrs.TPid, attrs.DestinationId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(dsts) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = utils.TPDestination{attrs.TPid, dsts[0].Id, dsts[0].Prefixes}
}
return nil
}
type AttrGetTPDestinationIds struct {
TPid string // Tariff plan id
}
// Queries destination identities on specific tariff plan.
func (self *ApierV1) GetTPDestinationIds(attrs AttrGetTPDestinationIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_DESTINATIONS, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
func (self *ApierV1) RemTPDestination(attrs AttrGetTPDestination, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "DestinationId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_DESTINATIONS, attrs.TPid, attrs.DestinationId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

92
apier/tprates.go Normal file
View File

@@ -0,0 +1,92 @@
/*
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 apier
// This file deals with tp_rates management over APIs
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new rate within a tariff plan
func (self *ApierV1) SetTPRate(attrs utils.TPRate, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RateId", "RateSlots"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.SetTPRates(attrs.TPid, map[string][]*utils.RateSlot{attrs.RateId: attrs.RateSlots}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPRate struct {
TPid string // Tariff plan id
RateId string // Rate id
}
// Queries specific Rate on tariff plan
func (self *ApierV1) GetTPRate(attrs AttrGetTPRate, reply *utils.TPRate) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RateId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if rts, err := self.StorDb.GetTpRates(attrs.TPid, attrs.RateId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(rts) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = *rts[attrs.RateId]
}
return nil
}
type AttrGetTPRateIds struct {
TPid string // Tariff plan id
}
// Queries rate identities on specific tariff plan.
func (self *ApierV1) GetTPRateIds(attrs AttrGetTPRateIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_RATES, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific Rate on Tariff plan
func (self *ApierV1) RemTPRate(attrs AttrGetTPRate, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RateId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_RATES, attrs.TPid, attrs.RateId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

92
apier/tpratingplans.go Normal file
View File

@@ -0,0 +1,92 @@
/*
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 apier
// This file deals with tp_destrates_timing management over APIs
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// Creates a new DestinationRateTiming profile within a tariff plan
func (self *ApierV1) SetTPRatingPlan(attrs utils.TPRatingPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId", "RatingPlanBindings"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.SetTPRatingPlans(attrs.TPid, map[string][]*utils.TPRatingPlanBinding{attrs.RatingPlanId: attrs.RatingPlanBindings}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPRatingPlan struct {
TPid string // Tariff plan id
RatingPlanId string // Rate id
}
// Queries specific RatingPlan profile on tariff plan
func (self *ApierV1) GetTPRatingPlan(attrs AttrGetTPRatingPlan, reply *utils.TPRatingPlan) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if rps, err := self.StorDb.GetTpRatingPlans(attrs.TPid, attrs.RatingPlanId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(rps) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = utils.TPRatingPlan{TPid: attrs.TPid, RatingPlanId: attrs.RatingPlanId, RatingPlanBindings: rps[attrs.RatingPlanId]}
}
return nil
}
type AttrGetTPRatingPlanIds struct {
TPid string // Tariff plan id
}
// Queries RatingPlan identities on specific tariff plan.
func (self *ApierV1) GetTPRatingPlanIds(attrs AttrGetTPRatingPlanIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_RATING_PLANS, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific RatingPlan on Tariff plan
func (self *ApierV1) RemTPRatingPlan(attrs AttrGetTPRatingPlan, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "RatingPlanId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_RATING_PLANS, attrs.TPid, attrs.RatingPlanId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

105
apier/tpratingprofiles.go Normal file
View File

@@ -0,0 +1,105 @@
/*
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 apier
// This file deals with tp_rate_profiles management over APIs
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/utils"
)
// 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 {
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 {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPRatingProfile struct {
TPid string // Tariff plan id
LoadId string // RatingProfile id
}
// Queries specific RatingProfile on tariff plan
func (self *ApierV1) GetTPRatingProfiles(attrs utils.TPRatingProfile, reply *[]*utils.TPRatingProfile) error {
mndtryFlds := []string{"TPid", "LoadId"}
if len(attrs.Subject) != 0 { // If Subject provided as filter, make all related fields mandatory
mndtryFlds = append(mndtryFlds, "Tenant", "TOR", "Direction", "Subject")
}
if missing := utils.MissingStructFields(&attrs, mndtryFlds); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if dr, err := self.StorDb.GetTpRatingProfiles(&attrs); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if dr == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
var rpfs []*utils.TPRatingProfile
if len(attrs.Subject) != 0 {
rpfs = []*utils.TPRatingProfile{dr[attrs.KeyId()]}
} else {
for _, rpfLst := range dr {
rpfs = append(rpfs, rpfLst)
}
}
*reply = rpfs
}
return nil
}
// Queries RatingProfile identities on specific tariff plan.
func (self *ApierV1) GetTPRatingProfileLoadIds(attrs utils.AttrTPRatingProfileIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_RATE_PROFILES, "loadid", map[string]string{
"tenant": attrs.Tenant,
"tor": attrs.TOR,
"direction": attrs.Direction,
"subject": attrs.Subject,
}); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific RatingProfile on Tariff plan
func (self *ApierV1) RemTPRatingProfile(attrs utils.TPRatingProfile, reply *string) error {
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 {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

94
apier/tptimings.go Normal file
View File

@@ -0,0 +1,94 @@
/*
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 apier
import (
"errors"
"fmt"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
// Creates a new timing within a tariff plan
func (self *ApierV1) SetTPTiming(attrs utils.ApierTPTiming, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "TimingId", "Years", "Months", "MonthDays", "WeekDays", "Time"}); len(missing) != 0 {
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
tm := engine.NewTiming(attrs.TimingId, attrs.Years, attrs.Months, attrs.MonthDays, attrs.WeekDays, attrs.Time)
if err := self.StorDb.SetTPTiming(attrs.TPid, tm); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
}
*reply = "OK"
return nil
}
type AttrGetTPTiming struct {
TPid string // Tariff plan id
TimingId string // Timing id
}
// Queries specific Timing on Tariff plan
func (self *ApierV1) GetTPTiming(attrs AttrGetTPTiming, reply *utils.ApierTPTiming) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "TimingId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if tms, err := self.StorDb.GetTpTimings(attrs.TPid, attrs.TimingId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if len(tms) == 0 {
return errors.New(utils.ERR_NOT_FOUND)
} else {
tm := tms[attrs.TimingId]
*reply = utils.ApierTPTiming{attrs.TPid, tm.Id, tm.Years.Serialize(";"),
tm.Months.Serialize(";"), tm.MonthDays.Serialize(";"), tm.WeekDays.Serialize(";"), tm.StartTime}
}
return nil
}
type AttrGetTPTimingIds struct {
TPid string // Tariff plan id
}
// Queries timing identities on specific tariff plan.
func (self *ApierV1) GetTPTimingIds(attrs AttrGetTPTimingIds, reply *[]string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_TIMINGS, "id", nil); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else if ids == nil {
return errors.New(utils.ERR_NOT_FOUND)
} else {
*reply = ids
}
return nil
}
// Removes specific Timing on Tariff plan
func (self *ApierV1) RemTPTiming(attrs AttrGetTPTiming, reply *string) error {
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "TimingId"}); len(missing) != 0 { //Params missing
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
}
if err := self.StorDb.RemTPData(utils.TBL_TP_TIMINGS, attrs.TPid, attrs.TimingId); err != nil {
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
} else {
*reply = "OK"
}
return nil
}

View File

@@ -0,0 +1,285 @@
/*
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 apier
import (
"fmt"
"net/rpc/jsonrpc"
"os"
"os/exec"
"path"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
var fscsvCfgPath string
var fscsvCfg *config.CGRConfig
func init() {
fscsvCfgPath = path.Join(*dataDir, "tutorials", "fs_csv", "cgrates", "etc", "cgrates", "cgrates.cfg")
fscsvCfg, _ = config.NewCGRConfig(&fscsvCfgPath)
}
// Remove here so they can be properly created by init script
func TestFsCsvRemoveDirs(t *testing.T) {
if !*testLocal {
return
}
for _, pathDir := range []string{cfg.CdreDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
if err := os.RemoveAll(pathDir); err != nil {
t.Fatal("Error removing folder: ", pathDir, err)
}
}
}
// Empty tables before using them
func TestFsCsvCreateTables(t *testing.T) {
if !*testLocal {
return
}
if *storDbType != utils.MYSQL {
t.Fatal("Unsupported storDbType")
}
var mysql *engine.MySQLStorage
if d, err := engine.NewMySQLStorage(fscsvCfg.StorDBHost, fscsvCfg.StorDBPort, fscsvCfg.StorDBName, fscsvCfg.StorDBUser, fscsvCfg.StorDBPass); err != nil {
t.Fatal("Error on opening database connection: ", err)
} 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} {
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
}
}
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 {
t.Fatal(err.Error())
}
}
}
func TestFsCsvInitDataDb(t *testing.T) {
if !*testLocal {
return
}
ratingDb, err := engine.ConfigureRatingStorage(fscsvCfg.RatingDBType, fscsvCfg.RatingDBHost, fscsvCfg.RatingDBPort, fscsvCfg.RatingDBName, fscsvCfg.RatingDBUser, fscsvCfg.RatingDBPass, fscsvCfg.DBDataEncoding)
if err != nil {
t.Fatal("Cannot connect to dataDb", err)
}
accountDb, err := engine.ConfigureAccountingStorage(fscsvCfg.AccountDBType, fscsvCfg.AccountDBHost, fscsvCfg.AccountDBPort, fscsvCfg.AccountDBName,
fscsvCfg.AccountDBUser, fscsvCfg.AccountDBPass, fscsvCfg.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 TestFsCsvStartFs(t *testing.T) {
if !*testLocal {
return
}
exec.Command("pkill", "freeswitch").Run() // Just to make sure no freeswitch is running
go func() {
fs := exec.Command("sudo", "/usr/share/cgrates/tutorials/fs_csv/freeswitch/etc/init.d/freeswitch", "start")
out, _ := fs.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsCsv: %s", out))
}()
time.Sleep(time.Duration(*waitFs) * time.Millisecond) // Give time to rater to fire up
}
// Finds cgr-engine executable and starts it with default configuration
func TestFsCsvStartEngine(t *testing.T) {
if !*testLocal {
return
}
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
go func() {
eng := exec.Command("sudo", "/usr/share/cgrates/tutorials/fs_json/cgrates/etc/init.d/cgrates", "start")
out, _ := eng.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsCsv: %s", out))
}()
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time to rater to fire up
}
// Connect rpc client to rater
func TestFsCsvRpcConn(t *testing.T) {
if !*testLocal {
return
}
var err error
rater, err = jsonrpc.Dial("tcp", fscsvCfg.RPCJSONListen)
if err != nil {
t.Fatal("Could not connect to rater: ", err.Error())
}
}
// Make sure we start with fresh data
func TestFsCsvEmptyCache(t *testing.T) {
if !*testLocal {
return
}
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 0, RatingPlans: 0, RatingProfiles: 0, Actions: 0}
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(expectedStats, rcvStats) {
t.Errorf("Calling ApierV1.GetCacheStats expected: %v, received: %v", expectedStats, rcvStats)
}
}
func TestFsCsvLoadTariffPlans(t *testing.T) {
if !*testLocal {
return
}
reply := ""
// Simple test that command is executed without errors
attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tutorials", "fs_csv", "cgrates", "tariffplans")}
if err := rater.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(100 * time.Millisecond) // Give time for scheduler to execute topups
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 1, RatingProfiles: 1, Actions: 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(expectedStats, rcvStats) {
t.Errorf("Calling ApierV1.GetCacheStats expected: %v, received: %v", expectedStats, rcvStats)
}
}
func TestFsCsvGetAccount(t *testing.T) {
if !*testLocal {
return
}
var reply *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", 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())
}
}
func TestFsCsvCall1(t *testing.T) {
if !*testLocal {
return
}
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,
}
var cc engine.CallCost
// Make sure the cost is what we expect it is
if err := rater.Call("Responder.GetCost", cd, &cc); err != nil {
t.Error("Got error on Responder.GetCost: ", err.Error())
} else if cc.GetConnectFee() != 0.4 && cc.Cost != 0.6 {
t.Errorf("Calling Responder.GetCost got callcost: %v", cc)
}
// Make sure debit charges what cost returned
if err := rater.Call("Responder.MaxDebit", cd, &cc); err != nil {
t.Error("Got error on Responder.MaxDebit: ", err.Error())
} else if cc.GetConnectFee() != 0.4 && cc.Cost != 0.6 {
t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc)
}
// 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"}
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 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
}
// Make sure debit charges what cost returned
if err := rater.Call("Responder.MaxDebit", cd, &cc); err != nil {
t.Error("Got error on Responder.MaxDebit: ", err.Error())
} else if cc.GetConnectFee() != 0.4 && cc.Cost != 0.2 { // Does not contain connectFee, however connectFee should be correctly reported
t.Errorf("Calling Responder.MaxDebit got callcost: %v", cc)
}
// Make sure the account was debited correctly for the first loop index (ConnectFee included)
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 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)
}
}
// Simply kill the engine after we are done with tests within this file
func TestFsCsvStopEngine(t *testing.T) {
if !*testLocal {
return
}
go func() {
eng := exec.Command("/usr/share/cgrates/tutorials/fs_csv/cgrates/etc/init.d/cgrates", "stop")
out, _ := eng.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsCsv: %s", out))
}()
}
func TestFsCsvStopFs(t *testing.T) {
if !*testLocal {
return
}
go func() {
fs := exec.Command("/usr/share/cgrates/tutorials/fs_csv/freeswitch/etc/init.d/freeswitch", "stop")
out, _ := fs.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsCsv: %s", out))
}()
}

View File

@@ -0,0 +1,504 @@
/*
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 apier
import (
"flag"
"fmt"
"net/rpc/jsonrpc"
"os"
"os/exec"
"path"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
)
var fsjsonCfgPath string
var fsjsonCfg *config.CGRConfig
var waitFs = flag.Int("wait_fs", 500, "Number of miliseconds to wait for FreeSWITCH to start")
func init() {
fsjsonCfgPath = path.Join(*dataDir, "tutorials", "fs_json", "cgrates", "etc", "cgrates", "cgrates.cfg")
fsjsonCfg, _ = config.NewCGRConfig(&fsjsonCfgPath)
}
// Remove here so they can be properly created by init script
func TestFsJsonRemoveDirs(t *testing.T) {
if !*testLocal {
return
}
for _, pathDir := range []string{fsjsonCfg.CdreDir, fsjsonCfg.HistoryDir} {
if err := os.RemoveAll(pathDir); err != nil {
t.Fatal("Error removing folder: ", pathDir, err)
}
}
}
// Empty tables before using them
func TestFsJsonCreateTables(t *testing.T) {
if !*testLocal {
return
}
if *storDbType != utils.MYSQL {
t.Fatal("Unsupported storDbType")
}
var mysql *engine.MySQLStorage
if d, err := engine.NewMySQLStorage(fsjsonCfg.StorDBHost, fsjsonCfg.StorDBPort, fsjsonCfg.StorDBName, fsjsonCfg.StorDBUser, fsjsonCfg.StorDBPass); err != nil {
t.Fatal("Error on opening database connection: ", err)
} 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
}
}
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 {
t.Fatal(err.Error())
}
}
}
func TestFsJsonInitDataDb(t *testing.T) {
if !*testLocal {
return
}
ratingDb, err := engine.ConfigureRatingStorage(fsjsonCfg.RatingDBType, fsjsonCfg.RatingDBHost, fsjsonCfg.RatingDBPort, fsjsonCfg.RatingDBName, fsjsonCfg.RatingDBUser, fsjsonCfg.RatingDBPass, fsjsonCfg.DBDataEncoding)
if err != nil {
t.Fatal("Cannot connect to dataDb", err)
}
accountDb, err := engine.ConfigureAccountingStorage(fsjsonCfg.AccountDBType, fsjsonCfg.AccountDBHost, fsjsonCfg.AccountDBPort, fsjsonCfg.AccountDBName,
fsjsonCfg.AccountDBUser, fsjsonCfg.AccountDBPass, fsjsonCfg.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 TestFsJsonStartFs(t *testing.T) {
if !*testLocal {
return
}
exec.Command("pkill", "freeswitch").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
go func() {
fs := exec.Command("/usr/share/cgrates/tutorials/fs_json/freeswitch/etc/init.d/freeswitch", "start")
out, _ := fs.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsJson: %s", out))
}()
time.Sleep(time.Duration(*waitFs) * time.Millisecond) // Give time to rater to fire up
}
// Finds cgr-engine executable and starts it with default configuration
func TestFsJsonStartEngine(t *testing.T) {
if !*testLocal {
return
}
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
go func() {
eng := exec.Command("/usr/share/cgrates/tutorials/fs_json/cgrates/etc/init.d/cgrates", "start")
out, _ := eng.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsJson: %s", out))
}()
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time to rater to fire up
}
// Connect rpc client to rater
func TestFsJsonRpcConn(t *testing.T) {
if !*testLocal {
return
}
var err error
rater, err = jsonrpc.Dial("tcp", fsjsonCfg.RPCJSONListen)
if err != nil {
t.Fatal("Could not connect to rater: ", err.Error())
}
}
// Make sure we start with fresh data
func TestFsJsonEmptyCache(t *testing.T) {
if !*testLocal {
return
}
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 0, RatingPlans: 0, RatingProfiles: 0, Actions: 0}
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(expectedStats, rcvStats) {
t.Errorf("Calling ApierV1.GetCacheStats expected: %v, received: %v", expectedStats, rcvStats)
}
}
func TestFsJsonLoadTariffPlans(t *testing.T) {
if !*testLocal {
return
}
reply := ""
// Simple test that command is executed without errors
attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tutorials", "fs_json", "cgrates", "tariffplans")}
if err := rater.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
var rcvStats *utils.CacheStats
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 2, RatingProfiles: 2, Actions: 5, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1}
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(expectedStats, rcvStats) {
t.Errorf("Calling ApierV1.GetCacheStats expected: %v, received: %v", expectedStats, rcvStats)
}
}
func TestFsJsonGetAccount1001(t *testing.T) {
if !*testLocal {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", 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 len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 2 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
}
blncLst := acnt.BalanceMap[attrs.BalanceType+attrs.Direction]
for _, blnc := range blncLst {
if len(blnc.SharedGroup) == 0 && blnc.Value != 5 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
} else if blnc.SharedGroup == "SHARED_A" && blnc.Value != 5 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
}
}
}
func TestFsJsonGetAccount1002(t *testing.T) {
if !*testLocal {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1002", BalanceType: "*monetary", 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 len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
if blnc.Value != 10 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}
func TestFsJsonGetAccount1003(t *testing.T) {
if !*testLocal {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1003", BalanceType: "*monetary", 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 len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
if blnc.Value != 10 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}
func TestFsJsonGetAccount1004(t *testing.T) {
if !*testLocal {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1004", BalanceType: "*monetary", 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 len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
if blnc.Value != 10 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}
func TestFsJsonGetAccount1006(t *testing.T) {
if !*testLocal {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1006", BalanceType: "*monetary", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err == nil {
t.Error("Got no error when querying unexisting balance")
}
}
func TestFsJsonGetAccount1007(t *testing.T) {
if !*testLocal {
return
}
var acnt *engine.Account
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1007", BalanceType: "*monetary", 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 len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
}
blncLst := acnt.BalanceMap[attrs.BalanceType+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)
} else if blnc.SharedGroup == "SHARED_A" && blnc.Value != 0 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
}
}
}
func TestMaxCallDuration(t *testing.T) {
if !*testLocal {
return
}
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
}
var remainingDurationFloat float64
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
} else {
remainingDuration := time.Duration(remainingDurationFloat)
if remainingDuration < time.Duration(90)*time.Minute {
t.Errorf("Expecting maxSessionTime around 1h30m, received as: %v", remainingDuration)
}
}
cd = engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Subject: "1002",
Account: "1002",
Destination: "1001",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
}
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
} else {
remainingDuration := time.Duration(remainingDurationFloat)
if remainingDuration < time.Duration(45)*time.Minute {
t.Errorf("Expecting maxSessionTime around 45m, received as: %v", remainingDuration)
}
}
cd = engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Subject: "1006",
Account: "1006",
Destination: "1001",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
}
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
} else {
remainingDuration := time.Duration(remainingDurationFloat)
if remainingDuration < time.Duration(45)*time.Minute {
t.Errorf("Expecting maxSessionTime around 45m, received as: %v", remainingDuration)
}
}
// 1007 should use the 1001 balance when doing maxSessionTime
cd = engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Subject: "1007",
Account: "1007",
Destination: "1001",
TimeStart: time.Now(),
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
}
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
t.Error(err)
} else {
remainingDuration := time.Duration(remainingDurationFloat)
if remainingDuration < time.Duration(20)*time.Minute {
t.Errorf("Expecting maxSessionTime around 20m, received as: %v", remainingDuration)
}
}
}
func TestMaxDebit1001(t *testing.T) {
if !*testLocal {
return
}
cc := &engine.CallCost{}
var acnt *engine.Account
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Subject: "1001",
Account: "1001",
Destination: "1002",
TimeStart: time.Now(),
TimeEnd: time.Now().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"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else {
if len(acnt.BalanceMap["*monetary*out"]) != 2 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap["*monetary*out"]))
}
blncLst := acnt.BalanceMap["*monetary*out"]
for _, blnc := range blncLst {
if blnc.SharedGroup == "SHARED_A" && blnc.Value != 5 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
} else if len(blnc.SharedGroup) == 0 && blnc.Value != 4.4 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}
}
}
func TestMaxDebit1007(t *testing.T) {
if !*testLocal {
return
}
cc := &engine.CallCost{}
var acnt *engine.Account
cd := engine.CallDescriptor{
Direction: "*out",
Tenant: "cgrates.org",
TOR: "call",
Subject: "1007",
Account: "1007",
Destination: "1002",
TimeStart: time.Now(),
TimeEnd: time.Now().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())
}
// Debit out of shared balance should reflect in the 1001 instead of 1007
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
} else {
if len(acnt.BalanceMap["*monetary*out"]) != 2 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap["*monetary*out"]))
}
blncLst := acnt.BalanceMap["*monetary*out"]
for _, blnc := range blncLst {
if blnc.SharedGroup == "SHARED_A" && blnc.Value != 4 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
} else if len(blnc.SharedGroup) == 0 && blnc.Value != 4.4 {
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
}
}
}
// Make sure 1007 remains the same
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "1007", BalanceType: "*monetary", 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 len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
}
blnc := acnt.BalanceMap[attrs.BalanceType+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 {
t.Errorf("Unexpected value for shared balance: %f", blnc.Value)
}
}
// Simply kill the engine after we are done with tests within this file
func TestFsJsonStopEngine(t *testing.T) {
if !*testLocal {
return
}
go func() {
eng := exec.Command("/usr/share/cgrates/tutorials/fs_json/cgrates/etc/init.d/cgrates", "stop")
out, _ := eng.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsJson: %s", out))
}()
}
func TestFsJsonStopFs(t *testing.T) {
if !*testLocal {
return
}
go func() {
fs := exec.Command("/usr/share/cgrates/tutorials/fs_json/freeswitch/etc/init.d/freeswitch", "stop")
out, _ := fs.CombinedOutput()
engine.Logger.Info(fmt.Sprintf("CgrEngine-TestFsJson: %s", out))
}()
}

View File

@@ -1,12 +1,14 @@
#! /usr/bin/env sh
go install github.com/cgrates/cgrates/cmd/cgr-rater
go install github.com/cgrates/cgrates/cmd/cgr-engine
cr=$?
go install github.com/cgrates/cgrates/cmd/cgr-loader
cl=$?
go install github.com/cgrates/cgrates/cmd/cgr-console
cc=$?
go install github.com/cgrates/cgrates/cmd/cgr-tester
ct=$?
exit $cr || $cl || $cc
exit $cr || $cl || $cc || $ct

View File

@@ -3,6 +3,7 @@ package cache2go
import (
"errors"
"strings"
"sync"
"time"
)
@@ -10,6 +11,7 @@ import (
type expiringCacheEntry interface {
XCache(key string, expire time.Duration, value expiringCacheEntry)
timer() *time.Timer
age() time.Duration
KeepAlive()
}
@@ -20,13 +22,19 @@ type XEntry struct {
key string
keepAlive bool
expireDuration time.Duration
timestamp time.Time
t *time.Timer
}
type timestampedValue struct {
timestamp time.Time
value interface{}
}
var (
xcache = make(map[string]expiringCacheEntry)
xMux sync.RWMutex
cache = make(map[string]interface{})
cache = make(map[string]timestampedValue)
mux sync.RWMutex
)
@@ -35,6 +43,7 @@ func (xe *XEntry) XCache(key string, expire time.Duration, value expiringCacheEn
xe.keepAlive = true
xe.key = key
xe.expireDuration = expire
xe.timestamp = time.Now()
xMux.Lock()
xcache[key] = value
xMux.Unlock()
@@ -62,6 +71,11 @@ func (xe *XEntry) timer() *time.Timer {
return xe.t
}
func (xe *XEntry) age() time.Duration {
return time.Since(xe.timestamp)
}
// Mark entry to be kept another expirationDuration period
func (xe *XEntry) KeepAlive() {
xe.Lock()
@@ -84,7 +98,7 @@ func GetXCached(key string) (ece expiringCacheEntry, err error) {
func Cache(key string, value interface{}) {
mux.Lock()
defer mux.Unlock()
cache[key] = value
cache[key] = timestampedValue{time.Now(), value}
}
// The function to extract a value for a key that never expire
@@ -92,11 +106,70 @@ func GetCached(key string) (v interface{}, err error) {
mux.RLock()
defer mux.RUnlock()
if r, ok := cache[key]; ok {
return r, nil
return r.value, nil
}
return nil, errors.New("not found")
}
func GetKeyAge(key string) (time.Duration, error) {
mux.RLock()
defer mux.RUnlock()
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 {
return r.age(), nil
}
return 0, errors.New("not found")
}
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)
}
}
}
func XRemKey(key string) {
xMux.Lock()
defer xMux.Unlock()
if r, ok := xcache[key]; ok {
if r.timer() != nil {
r.timer().Stop()
}
}
delete(xcache, key)
}
func XRemPrefixKey(prefix string) {
xMux.Lock()
defer xMux.Unlock()
for key, _ := range xcache {
if strings.HasPrefix(key, prefix) {
if r, ok := xcache[key]; ok {
if r.timer() != nil {
r.timer().Stop()
}
}
delete(xcache, key)
}
}
}
// Delete all keys from expiraton cache
func XFlush() {
xMux.Lock()
@@ -113,5 +186,23 @@ func XFlush() {
func Flush() {
mux.Lock()
defer mux.Unlock()
cache = make(map[string]interface{})
cache = make(map[string]timestampedValue)
}
func CountEntries(prefix string) (result int) {
for key, _ := range cache {
if strings.HasPrefix(key, prefix) {
result++
}
}
return
}
func XCountEntries(prefix string) (result int) {
for key, _ := range xcache {
if strings.HasPrefix(key, prefix) {
result++
}
}
return
}

View File

@@ -73,3 +73,71 @@ func TestFlushNoTimout(t *testing.T) {
t.Error("Error expiring data")
}
}
func TestRemKey(t *testing.T) {
Cache("t1", "test")
if t1, err := GetCached("t1"); err != nil || t1 != "test" {
t.Error("Error setting cache")
}
RemKey("t1")
if t1, err := GetCached("t1"); err == nil || t1 == "test" {
t.Error("Error removing cached key")
}
}
func TestXRemKey(t *testing.T) {
a := &myStruct{data: "mama are mere"}
a.XCache("mama", 10*time.Second, a)
if t1, err := GetXCached("mama"); err != nil || t1 != a {
t.Error("Error setting xcache")
}
XRemKey("mama")
if t1, err := GetXCached("mama"); err == nil || t1 == a {
t.Error("Error removing xcached key: ", err, t1)
}
}
/*
These tests sometimes fails on drone.io
func TestGetKeyAge(t *testing.T) {
Cache("t1", "test")
d, err := GetKeyAge("t1")
if err != nil || d > time.Millisecond || d < time.Nanosecond {
t.Error("Error getting cache key age: ", d)
}
}
func TestXGetKeyAge(t *testing.T) {
a := &myStruct{data: "mama are mere"}
a.XCache("t1", 10*time.Second, a)
d, err := GetXKeyAge("t1")
if err != nil || d > time.Millisecond || d < time.Nanosecond {
t.Error("Error getting cache key age: ", d)
}
}
*/
func TestRemPrefixKey(t *testing.T) {
Cache("x_t1", "test")
Cache("y_t1", "test")
RemPrefixKey("x_")
_, errX := GetCached("x_t1")
_, errY := GetCached("y_t1")
if errX == nil || errY != nil {
t.Error("Error removing prefix: ", errX, errY)
}
}
func TestXRemPrefixKey(t *testing.T) {
a := &myStruct{data: "mama are mere"}
a.XCache("x_t1", 10*time.Second, a)
a.XCache("y_t1", 10*time.Second, a)
XRemPrefixKey("x_")
_, errX := GetXCached("x_t1")
_, errY := GetXCached("y_t1")
if errX == nil || errY != nil {
t.Error("Error removing prefix: ", errX, errY)
}
}

261
cdrc/cdrc.go Normal file
View File

@@ -0,0 +1,261 @@
/*
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 cdrc
import (
"bufio"
"encoding/csv"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/cgrates/cgrates/cdrs"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"github.com/howeyc/fsnotify"
)
const (
CSV = "csv"
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)
}
}
if err := cdrc.parseFieldsConfig(); err != nil {
return nil, err
}
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
}
// 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
return self.trackCDRFiles()
}
// No automated, process and sleep approach
for {
self.processCdrDir()
time.Sleep(self.cgrCfg.CdrcRunDelay)
}
}
// 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}
var err error
for cfgFieldName, cfgFieldVal := range self.cfgCdrFields {
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]
}
} 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
case utils.ACCOUNT:
ratedCdr.Account = fieldVal
case utils.SUBJECT:
ratedCdr.Subject = fieldVal
case utils.DESTINATION:
ratedCdr.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())
}
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())
}
case utils.DURATION:
if ratedCdr.Duration, err = utils.ParseDurationWithSecs(fieldVal); err != nil {
return nil, fmt.Errorf("Cannot parse duration field, err: %s", err.Error())
}
default: // Extra fields will not match predefined so they all show up here
ratedCdr.ExtraFields[cfgFieldName] = fieldVal
}
}
return ratedCdr, 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)
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
}
}
}
return nil
}
// Watch the specified folder for file moves and parse the files on events
func (self *Cdrc) trackCDRFiles() (err error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return
}
defer watcher.Close()
err = watcher.Watch(self.cgrCfg.CdrcCdrInDir)
if err != nil {
return
}
engine.Logger.Info(fmt.Sprintf("<Cdrc> Monitoring %s for file moves.", self.cgrCfg.CdrcCdrInDir))
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()))
}
}
case err := <-watcher.Error:
engine.Logger.Err(fmt.Sprintf("Inotify error: %s", err.Error()))
}
}
}
// Processe file at filePath and posts the valid cdr rows out of it
func (self *Cdrc) processFile(filePath string) error {
_, fn := path.Split(filePath)
engine.Logger.Info(fmt.Sprintf("<Cdrc> Parsing: %s", filePath))
file, err := os.Open(filePath)
defer file.Close()
if err != nil {
engine.Logger.Crit(err.Error())
return err
}
csvReader := csv.NewReader(bufio.NewReader(file))
for {
record, err := csvReader.Read()
if err != nil && err == io.EOF {
break // End of file
} else 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)
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()))
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()))
continue
}
}
}
// Finished with file, move it to processed folder
newPath := path.Join(self.cgrCfg.CdrcCdrOutDir, 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))
return nil
}

155
cdrc/cdrc_local_test.go Normal file
View File

@@ -0,0 +1,155 @@
/*
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 cdrc
import (
"errors"
"flag"
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/utils"
"io/ioutil"
"os/exec"
"os"
"path"
"testing"
"time"
)
/*
README:
Enable local tests by passing '-local' to the go test command
It is expected that the data folder of CGRateS exists at path /usr/share/cgrates/data or passed via command arguments.
Prior running the tests, create database and users by running:
mysql -pyourrootpwd < /usr/share/cgrates/data/storage/mysql/create_db_with_users.sql
What these tests do:
* Flush tables in storDb.
* Start engine with default configuration and give it some time to listen (here caching can slow down).
*
*/
var cfgPath string
var cfg *config.CGRConfig
var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args
var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here")
var storDbType = flag.String("stordb_type", "mysql", "The type of the storDb database <mysql>")
var waitRater = flag.Int("wait_rater", 300, "Number of miliseconds to wait for rater to start and cache")
func init() {
cfgPath = path.Join(*dataDir, "conf", "samples", "apier_local_test.cfg")
cfg, _ = config.NewCGRConfig(&cfgPath)
}
var fileContent1 = `accid11,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
accid12,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
dummy_data
accid13,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
`
var fileContent2 = `accid21,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
accid22,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
accid23,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)
if err := engine.Start(); err != nil {
return fmt.Errorf("Cannot start cgr-engine: %s", err.Error())
}
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time to rater to fire up
return nil
}
func stopEngine() error {
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
return nil
}
func TestEmptyTables(t *testing.T) {
if !*testLocal {
return
}
if *storDbType != utils.MYSQL {
t.Fatal("Unsupported storDbType")
}
var mysql *engine.MySQLStorage
if d, err := engine.NewMySQLStorage(cfg.StorDBHost, cfg.StorDBPort, cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass); err != nil {
t.Fatal("Error on opening database connection: ", err)
} 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} {
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
}
}
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 {
t.Fatal(err.Error())
}
}
}
// Creates cdr files and starts the engine
func TestCreateCdrFiles(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, "file1.csv"), []byte(fileContent1), 0644); err != nil {
t.Fatal(err.Error)
}
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file2.csv"), []byte(fileContent2), 0644); err != nil {
t.Fatal(err.Error)
}
}
func TestProcessCdrDir(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, nil)
if err != nil {
t.Fatal(err.Error())
}
if err := cdrc.processCdrDir(); err != nil {
t.Error(err)
}
stopEngine()
}

108
cdrc/cdrc_test.go Normal file
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 cdrc
import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"reflect"
"testing"
"time"
)
func TestParseFieldsConfig(t *testing.T) {
// Test default config
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)
}
cdrRow := []string{"firstField", "secondField"}
_, err := cdrc.recordAsStoredCdr(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)
if err != nil {
t.Error("Failed to parse CDR in rated cdr", err)
}
expectedCdr := &utils.StoredCdr{
CgrId: utils.FSCgrId(cdrRow[0]),
AccId: cdrRow[0],
CdrSource: cgrConfig.CdrcSourceId,
ReqType: cdrRow[1],
Direction: cdrRow[2],
Tenant: cdrRow[3],
TOR: cdrRow[4],
Account: cdrRow[5],
Subject: cdrRow[6],
Destination: cdrRow[7],
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,
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"))
}
*/
}

28
cdre/cdrexporter.go Normal file
View File

@@ -0,0 +1,28 @@
/*
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 (
"github.com/cgrates/cgrates/utils"
)
type CdrWriter interface {
WriteCdr(cdr *utils.StoredCdr) string
Close()
}

53
cdre/csv.go Normal file
View File

@@ -0,0 +1,53 @@
/*
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()
}

47
cdre/csv_test.go Normal file
View File

@@ -0,0 +1,47 @@
/*
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"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"strings"
"testing"
"time"
)
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,
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`
result := strings.TrimSpace(writer.String())
if result != expected {
t.Errorf("Expected: \n%s received: \n%s.", expected, result)
}
}

267
cdre/fixedwidth.go Normal file
View File

@@ -0,0 +1,267 @@
/*
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())
}
}

190
cdre/fixedwidth_test.go Normal file
View File

@@ -0,0 +1,190 @@
/*
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"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/utils"
"math"
"testing"
"time"
)
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: "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: "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"},
}
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: "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: "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: "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: "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"},
}
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: "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"},
}
// 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},
}
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",
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,
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
}
if err := fwWriter.WriteCdr(cdr); 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")
}
fwWriter.Close()
allOut := wrBuf.String()
eAllOut := eHeader + eContentOut + eTrailer
if math.Mod(float64(len(allOut)), 145) != 0 {
t.Error("Unexpected export content length", len(allOut))
} else if len(allOut) != len(eAllOut) {
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)
}
}
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},
}
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",
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,
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",
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,
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",
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,
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))
}
}
if len(wrBuf.String()) != 0 {
t.Errorf("Output buffer should be empty before write")
}
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 !fwWriter.lastCdrTime.Equal(cdr4.SetupTime) {
t.Error("Unexpected lastCdrTime in stats: ", fwWriter.lastCdrTime)
}
if fwWriter.numberOfRecords != 3 {
t.Error("Unexpected number of records in the stats: ", fwWriter.numberOfRecords)
}
if fwWriter.totalDuration != time.Duration(330)*time.Second {
t.Error("Unexpected total duration in the stats: ", fwWriter.totalDuration)
}
if fwWriter.totalCost != 5.9957 {
t.Error("Unexpected total cost in the stats: ", fwWriter.totalCost)
}
}

73
cdre/libfixedwidth.go Normal file
View File

@@ -0,0 +1,73 @@
/*
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 (
"errors"
"fmt"
)
// Used as generic function logic for various fields
// Attributes
// source - the base source
// width - the field width
// strip - if present it will specify the strip strategy, when missing strip will not be allowed
// padding - if present it will specify the padding strategy to use, left, right, zeroleft, zeroright
func FmtFieldWidth(source string, width int, strip, padding string, mandatory bool) (string, error) {
if mandatory && len(source) == 0 {
return "", errors.New("Empty source value")
}
if len(source) == width { // the source is exactly the maximum length
return source, nil
}
if len(source) > width { //the source is bigger than allowed
if len(strip) == 0 {
return "", fmt.Errorf("Source %s is bigger than the width %d, no strip defied", source, width)
}
if strip == "right" {
return source[:width], nil
} else if strip == "xright" {
return source[:width-1] + "x", nil // Suffix with x to mark prefix
} else if strip == "left" {
diffIndx := len(source) - width
return source[diffIndx:], nil
} else if strip == "xleft" { // Prefix one x to mark stripping
diffIndx := len(source) - width
return "x" + source[diffIndx+1:], nil
}
} else { //the source is smaller as the maximum allowed
if len(padding) == 0 {
return "", fmt.Errorf("Source %s is smaller than the width %d, no padding defined", source, width)
}
var paddingFmt string
switch padding {
case "right":
paddingFmt = fmt.Sprintf("%%-%ds", width)
case "left":
paddingFmt = fmt.Sprintf("%%%ds", width)
case "zeroleft":
paddingFmt = fmt.Sprintf("%%0%ds", width)
}
if len(paddingFmt) != 0 {
return fmt.Sprintf(paddingFmt, source), nil
}
}
return source, nil
}

116
cdre/libfixedwidth_test.go Normal file
View File

@@ -0,0 +1,116 @@
/*
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 (
"testing"
)
func TestMandatory(t *testing.T) {
_, err := FmtFieldWidth("", 0, "", "", true)
if err == nil {
t.Errorf("Failed to detect mandatory value")
}
}
func TestMaxLen(t *testing.T) {
result, err := FmtFieldWidth("test", 4, "", "", false)
expected := "test"
if err != nil || result != expected {
t.Errorf("Expected \"test\" was \"%s\"", result)
}
}
func TestRPadding(t *testing.T) {
result, err := FmtFieldWidth("test", 8, "", "right", false)
expected := "test "
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestPaddingFiller(t *testing.T) {
result, err := FmtFieldWidth("", 8, "", "right", false)
expected := " "
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestLPadding(t *testing.T) {
result, err := FmtFieldWidth("test", 8, "", "left", false)
expected := " test"
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestZeroLPadding(t *testing.T) {
result, err := FmtFieldWidth("test", 8, "", "zeroleft", false)
expected := "0000test"
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestRStrip(t *testing.T) {
result, err := FmtFieldWidth("test", 2, "right", "", false)
expected := "te"
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestXRStrip(t *testing.T) {
result, err := FmtFieldWidth("test", 3, "xright", "", false)
expected := "tex"
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestLStrip(t *testing.T) {
result, err := FmtFieldWidth("test", 2, "left", "", false)
expected := "st"
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestXLStrip(t *testing.T) {
result, err := FmtFieldWidth("test", 3, "xleft", "", false)
expected := "xst"
if err != nil || result != expected {
t.Errorf("Expected \"%s \" was \"%s\"", expected, result)
}
}
func TestStripNotAllowed(t *testing.T) {
_, err := FmtFieldWidth("test", 3, "", "", false)
if err == nil {
t.Error("Expected error")
}
}
func TestPaddingNotAllowed(t *testing.T) {
_, err := FmtFieldWidth("test", 5, "", "", false)
if err == nil {
t.Error("Expected error")
}
}

View File

@@ -19,128 +19,75 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package cdrs
import (
"encoding/json"
"fmt"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/rater"
"io/ioutil"
"net/http"
"strconv"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/mediator"
"github.com/cgrates/cgrates/utils"
)
const (
// Freswitch event proprities names
DIRECTION = "Call-Direction"
ORIG_ID = "variable_sip_call_id" //- originator_id - match cdrs
SUBJECT = "variable_cgr_subject"
ACCOUNT = "variable_cgr_account"
DESTINATION = "variable_cgr_destination"
REQTYPE = "variable_cgr_reqtype" //prepaid or postpaid
TOR = "variable_cgr_tor"
UUID = "Unique-ID" // -Unique ID for this call leg
CSTMID = "variable_cgr_cstmid"
CALL_DEST_NR = "Caller-Destination-Number"
PARK_TIME = "Caller-Profile-Created-Time"
START_TIME = "Caller-Channel-Answered-Time"
END_TIME = "Caller-Channel-Hangup-Time"
NAME = "Event-Name"
HEARTBEAT = "HEARTBEAT"
ANSWER = "CHANNEL_ANSWER"
HANGUP = "CHANNEL_HANGUP_COMPLETE"
PARK = "CHANNEL_PARK"
REQTYPE_PREPAID = "prepaid"
REQTYPE_POSTPAID = "postpaid"
AUTH_OK = "+AUTH_OK"
DISCONNECT = "+SWITCH DISCONNECT"
INSUFFICIENT_FUNDS = "-INSUFFICIENT_FUNDS"
MISSING_PARAMETER = "-MISSING_PARAMETER"
SYSTEM_ERROR = "-SYSTEM_ERROR"
MANAGER_REQUEST = "+MANAGER_REQUEST"
USERNAME = "Caller-Username"
var (
cfg *config.CGRConfig // Share the configuration with the rest of the package
storage engine.CdrStorage
medi *mediator.Mediator
)
var cfg *config.CGRConfig
// Returns first non empty string out of vals. Useful to extract defaults
func firstNonEmpty(vals ...string) string {
for _, val := range vals {
if len(val) != 0 {
return val
}
// Returns error if not able to properly store the CDR, mediation is async since we can always recover offline
func storeAndMediate(rawCdr utils.RawCDR) error {
if err := storage.SetCdr(rawCdr); err != nil {
return err
}
return ""
if cfg.CDRSMediator == utils.INTERNAL {
go func() {
if err := medi.RateCdr(rawCdr); err != nil {
engine.Logger.Err(fmt.Sprintf("Could not run mediation on CDR: %s", err.Error()))
}
}()
}
return nil
}
func GetName(vars map[string]string) string {
return vars[NAME]
}
func GetDirection(vars map[string]string) string {
//TODO: implement direction
return "OUT"
//return vars[DIRECTION]
}
func GetOrigId(vars map[string]string) string {
return vars[ORIG_ID]
}
func GetSubject(vars map[string]string) string {
return firstNonEmpty(vars[SUBJECT], vars[USERNAME])
}
func GetAccount(vars map[string]string) string {
return firstNonEmpty(vars[ACCOUNT], vars[USERNAME])
// Handler for generic cgr cdr http
func cgrCdrHandler(w http.ResponseWriter, r *http.Request) {
cgrCdr, err := utils.NewCgrCdrFromHttpReq(r)
if err != nil {
engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
}
if err := storeAndMediate(cgrCdr); err != nil {
engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
}
}
// Charging destination number
func GetDestination(vars map[string]string) string {
return firstNonEmpty(vars[DESTINATION], vars[CALL_DEST_NR])
}
// Original dialed destination number, useful in case of unpark
func GetCallDestNr(vars map[string]string) string {
return vars[CALL_DEST_NR]
}
func GetTOR(vars map[string]string) string {
return firstNonEmpty(vars[TOR], cfg.SMDefaultTOR)
}
func GetUUID(vars map[string]string) string {
return vars[UUID]
}
func GetTenant(vars map[string]string) string {
return firstNonEmpty(vars[CSTMID], cfg.SMDefaultTenant)
}
func GetReqType(vars map[string]string) string {
return firstNonEmpty(vars[REQTYPE], cfg.SMDefaultReqType)
}
func GetFallbackSubj(vars map[string]string) string {
return cfg.SMDefaultSubject
}
func GetStartTime(vars map[string]string, field string) (t time.Time, err error) {
st, err := strconv.ParseInt(vars[field], 0, 64)
t = time.Unix(0, st*1000)
return
}
func GetEndTime() (vars map[string]string, t time.Time, err error) {
st, err := strconv.ParseInt(vars[END_TIME], 0, 64)
t = time.Unix(0, st*1000)
return
}
type CDR struct {
Variables map[string]string
}
func cdrHandler(w http.ResponseWriter, r *http.Request) {
// Handler for fs http
func fsCdrHandler(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
cdr := CDR{}
if err := json.Unmarshal(body, &cdr); err == nil {
} else {
rater.Logger.Err(fmt.Sprintf("CDRCAPTOR: Could not unmarshal cdr: %v", err))
fsCdr, err := new(FSCdr).New(body)
if err != nil {
engine.Logger.Err(fmt.Sprintf("Could not create CDR entry: %s", err.Error()))
}
if err := storeAndMediate(fsCdr); err != nil {
engine.Logger.Err(fmt.Sprintf("Errors when storing CDR entry: %s", err.Error()))
}
}
func startCaptiuringCDRs() {
http.HandleFunc("/cdr", cdrHandler)
http.ListenAndServe(":8080", nil)
type CDRS struct{}
func New(s engine.CdrStorage, m *mediator.Mediator, c *config.CGRConfig) *CDRS {
storage = s
medi = m
cfg = c
return &CDRS{}
}
func (cdrs *CDRS) RegisterHanlersToServer(server *engine.Server) {
server.RegisterHttpFunc("/cgr", cgrCdrHandler)
server.RegisterHttpFunc("/freeswitch_json", fsCdrHandler)
}
// Used to internally process CDR
func (cdrs *CDRS) ProcessRawCdr(rawCdr utils.RawCDR) error {
return storeAndMediate(rawCdr)
}

310
cdrs/fscdr.go Normal file
View File

@@ -0,0 +1,310 @@
/*
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
}

182
cdrs/fscdr_test.go Normal file

File diff suppressed because one or more lines are too long

View File

@@ -19,10 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/cgrates/cgrates/console"
"github.com/cgrates/cgrates/rater"
"github.com/cgrates/cgrates/utils"
"log"
"net/rpc"
"net/rpc/jsonrpc"
@@ -32,13 +35,13 @@ import (
var (
version = flag.Bool("version", false, "Prints the application version.")
server = flag.String("server", "127.0.0.1:2012", "server address host:port")
rpc_encoding = flag.String("rpc_encoding", "gob", "RPC encoding used <gob|json>")
rpc_encoding = flag.String("rpc_encoding", "json", "RPC encoding used <gob|json>")
)
func main() {
flag.Parse()
if *version {
fmt.Println("CGRateS " + rater.VERSION)
fmt.Println("CGRateS " + utils.VERSION)
return
}
var client *rpc.Client
@@ -61,7 +64,9 @@ func main() {
}
res := cmd.RpcResult()
if rpcErr := client.Call(cmd.RpcMethod(), cmd.RpcParams(), res); rpcErr != nil {
fmt.Println("Error executing command: " + rpcErr.Error())
}
fmt.Println("Result:", res)
result, _ := json.MarshalIndent(res, "", " ")
fmt.Println(string(result))
}

View File

@@ -0,0 +1,473 @@
/*
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 main
import (
"errors"
"flag"
"fmt"
"log"
"net/rpc"
"os"
"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"
)
const (
JSON = "json"
GOB = "gob"
POSTGRES = "postgres"
MYSQL = "mysql"
MONGO = "mongo"
REDIS = "redis"
SAME = "same"
FS = "freeswitch"
)
var (
cfgPath = flag.String("config", "/etc/cgrates/cgrates.cfg", "Configuration file location.")
version = flag.Bool("version", false, "Prints the application version.")
raterEnabled = flag.Bool("rater", false, "Enforce starting of the rater daemon overwriting config")
schedEnabled = flag.Bool("scheduler", false, "Enforce starting of the scheduler daemon .overwriting config")
cdrsEnabled = flag.Bool("cdrs", false, "Enforce starting of the cdrs daemon overwriting config")
cdrcEnabled = flag.Bool("cdrc", false, "Enforce starting of the cdrc service overwriting config")
mediatorEnabled = flag.Bool("mediator", false, "Enforce starting of the mediator service overwriting config")
pidFile = flag.String("pid", "", "Write pid file")
bal = balancer2go.NewBalancer()
exitChan = make(chan bool)
server = &engine.Server{}
scribeServer history.Scribe
cdrServer *cdrs.CDRS
sm sessionmanager.SessionManager
medi *mediator.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 {
engine.Logger.Crit(fmt.Sprintf("Cache rating error: %s", err.Error()))
exitChan <- true
return
}
if err := accountDb.CacheAccounting(nil, nil, nil); err != nil {
engine.Logger.Crit(fmt.Sprintf("Cache accounting error: %s", err.Error()))
exitChan <- true
return
}
close(doneChan)
}
func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrDb engine.CdrStorage, cacheChan, chanDone chan struct{}) {
var connector engine.Connector
if cfg.MediatorRater == utils.INTERNAL {
<-cacheChan // Cache needs to come up before we are ready
connector = responder
} else {
var client *rpc.Client
var err error
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.MediatorRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i+1) * time.Second)
}
if err != nil {
engine.Logger.Crit(fmt.Sprintf("<Mediator> Could not connect to engine: %v", err))
exitChan <- true
return
}
connector = &engine.RPCClientConnector{Client: client}
}
var err error
medi, err = mediator.NewMediator(connector, loggerDb, cdrDb, 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})
close(chanDone)
}
func startCdrc(cdrsChan chan struct{}) {
if cfg.CdrcCdrs == utils.INTERNAL {
<-cdrsChan // Wait for CDRServer to come up before start processing
}
cdrc, err := cdrc.NewCdrc(cfg, cdrServer)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Cdrc config parsing error: %s", err.Error()))
exitChan <- true
return
}
if err := cdrc.Run(); err != nil {
engine.Logger.Crit(fmt.Sprintf("Cdrc run error: %s", err.Error()))
}
exitChan <- true // If run stopped, something is bad, stop the application
}
func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage, cacheChan chan struct{}) {
var connector engine.Connector
if cfg.SMRater == utils.INTERNAL {
<-cacheChan // Wait for the cache to init before start doing queries
connector = responder
} else {
var client *rpc.Client
var err error
for i := 0; i < cfg.SMRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.SMRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i+1) * time.Second)
}
if err != nil {
engine.Logger.Crit(fmt.Sprintf("<SessionManager> Could not connect to engine: %v", err))
exitChan <- true
}
connector = &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))
}
default:
engine.Logger.Err(fmt.Sprintf("<SessionManager> Unsupported session manger type: %s!", cfg.SMSwitchType))
}
exitChan <- true
}
func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage, mediChan, doneChan chan struct{}) {
if cfg.CDRSMediator == utils.INTERNAL {
<-mediChan // Deadlock if mediator not started
if medi == nil {
engine.Logger.Crit("<CDRS> Could not connect to mediator, exiting.")
exitChan <- true
return
}
}
cdrServer = cdrs.New(cdrDb, medi, cfg)
cdrServer.RegisterHanlersToServer(server)
close(doneChan)
}
func startHistoryServer(chanDone chan struct{}) {
if scribeServer, err = history.NewFileScribe(cfg.HistoryDir, cfg.HistorySaveInterval); err != nil {
engine.Logger.Crit(fmt.Sprintf("<HistoryServer> Could not start, error: %s", err.Error()))
exitChan <- true
return
}
server.RpcRegisterName("Scribe", scribeServer)
close(chanDone)
}
// chanStartServer will report when server is up, useful for internal requests
func startHistoryAgent(scribeServer history.Scribe, 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 {
case <-time.After(1 * time.Minute):
engine.Logger.Crit(fmt.Sprintf("<HistoryAgent> Timeout waiting for server to start."))
exitChan <- true
return
case <-chanServerStarted:
}
//<-chanServerStarted // If server is not enabled, will have deadlock here
} else { // Connect in iteration since there are chances of concurrency here
for i := 0; i < 3; i++ { //ToDo: Make it globally configurable
//engine.Logger.Crit(fmt.Sprintf("<HistoryAgent> Trying to connect, iteration: %d, time %s", i, time.Now()))
if scribeServer, err = history.NewProxyScribe(cfg.HistoryServer); err == nil {
break //Connected so no need to reiterate
} else if i == 2 && err != nil {
engine.Logger.Crit(fmt.Sprintf("<HistoryAgent> Could not connect to the server, error: %s", err.Error()))
exitChan <- true
return
}
time.Sleep(time.Duration(i) * time.Second)
}
}
engine.SetHistoryScribe(scribeServer)
return
}
// Starts the rpc server, waiting for the necessary components to finish their tasks
func serveRpc(rpcWaitChans []chan struct{}) {
for _, chn := range rpcWaitChans {
<-chn
}
// Each of the serve blocks so need to start in their own goroutine
go server.ServeJSON(cfg.RPCJSONListen)
go server.ServeGOB(cfg.RPCGOBListen)
}
// Starts the http server, waiting for the necessary components to finish their tasks
func serveHttp(httpWaitChans []chan struct{}) {
for _, chn := range httpWaitChans {
<-chn
}
server.ServeHTTP(cfg.HTTPListen)
}
func checkConfigSanity() error {
if cfg.SMEnabled && cfg.RaterEnabled && cfg.RaterBalancer != "" {
engine.Logger.Crit("The session manager must not be enabled on a worker engine (change [engine]/balancer to disabled)!")
return errors.New("SessionManager on Worker")
}
if cfg.BalancerEnabled && cfg.RaterEnabled && cfg.RaterBalancer != "" {
engine.Logger.Crit("The balancer is enabled so it cannot connect to another balancer (change rater/balancer to disabled)!")
return errors.New("Improperly configured balancer")
}
if cfg.CDRSEnabled && cfg.CDRSMediator == utils.INTERNAL && !cfg.MediatorEnabled {
engine.Logger.Crit("CDRS cannot connect to mediator, Mediator not enabled in configuration!")
return errors.New("Internal Mediator required by CDRS")
}
if cfg.HistoryServerEnabled && cfg.HistoryServer == utils.INTERNAL && !cfg.HistoryServerEnabled {
engine.Logger.Crit("The history agent is enabled and internal and history server is disabled!")
return errors.New("Improperly configured history service")
}
return nil
}
func writePid() {
engine.Logger.Info(*pidFile)
f, err := os.Create(*pidFile)
if err != nil {
log.Fatal("Could not write pid file: ", err)
}
f.WriteString(strconv.Itoa(os.Getpid()))
if err := f.Close(); err != nil {
log.Fatal("Could not write pid file: ", err)
}
}
func main() {
flag.Parse()
if *version {
fmt.Println("CGRateS " + utils.VERSION)
return
}
if *pidFile != "" {
writePid()
}
runtime.GOMAXPROCS(runtime.NumCPU())
cfg, err = config.NewCGRConfig(cfgPath)
if err != nil {
engine.Logger.Crit(fmt.Sprintf("Could not parse config: %s exiting!", err))
return
}
config.SetCgrConfig(cfg) // Share the config object
if *raterEnabled {
cfg.RaterEnabled = *raterEnabled
}
if *schedEnabled {
cfg.SchedulerEnabled = *schedEnabled
}
if *cdrsEnabled {
cfg.CDRSEnabled = *cdrsEnabled
}
if *cdrcEnabled {
cfg.CdrcEnabled = *cdrcEnabled
}
if *mediatorEnabled {
cfg.MediatorEnabled = *mediatorEnabled
}
// some consitency checks
errCfg := checkConfigSanity()
if errCfg != nil {
engine.Logger.Crit(errCfg.Error())
return
}
var ratingDb engine.RatingStorage
var accountDb engine.AccountingStorage
var logDb engine.LogStorage
var loadDb engine.LoadStorage
var cdrDb engine.CdrStorage
ratingDb, err = engine.ConfigureRatingStorage(cfg.RatingDBType, cfg.RatingDBHost, cfg.RatingDBPort,
cfg.RatingDBName, cfg.RatingDBUser, cfg.RatingDBPass, cfg.DBDataEncoding)
if err != nil { // Cannot configure getter database, show stopper
engine.Logger.Crit(fmt.Sprintf("Could not configure dataDb: %s exiting!", err))
return
}
defer ratingDb.Close()
engine.SetRatingStorage(ratingDb)
accountDb, err = engine.ConfigureAccountingStorage(cfg.AccountDBType, cfg.AccountDBHost, cfg.AccountDBPort,
cfg.AccountDBName, cfg.AccountDBUser, cfg.AccountDBPass, cfg.DBDataEncoding)
if err != nil { // Cannot configure getter database, show stopper
engine.Logger.Crit(fmt.Sprintf("Could not configure dataDb: %s exiting!", err))
return
}
defer accountDb.Close()
engine.SetAccountingStorage(accountDb)
if cfg.StorDBType == SAME {
logDb = ratingDb.(engine.LogStorage)
} else {
logDb, err = engine.ConfigureLogStorage(cfg.StorDBType, cfg.StorDBHost, cfg.StorDBPort,
cfg.StorDBName, cfg.StorDBUser, cfg.StorDBPass, cfg.DBDataEncoding)
if err != nil { // Cannot configure logger database, show stopper
engine.Logger.Crit(fmt.Sprintf("Could not configure logger database: %s exiting!", err))
return
}
}
defer logDb.Close()
engine.SetStorageLogger(logDb)
// loadDb,cdrDb and logDb are all mapped on the same stordb storage
loadDb = logDb.(engine.LoadStorage)
cdrDb = logDb.(engine.CdrStorage)
engine.SetRoundingMethodAndDecimals(cfg.RoundingMethod, cfg.RoundingDecimals)
if cfg.SMDebitInterval > 0 {
if dp, err := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval)); err == nil {
engine.SetDebitPeriod(dp)
}
}
stopHandled := false
// Async starts here
rpcWait := make([]chan struct{}, 0) // Rpc server will start as soon as this list is consumed
httpWait := make([]chan struct{}, 0) // Http server will start as soon as this list is consumed
var cacheChan chan struct{}
if cfg.RaterEnabled { // Cache rating if rater enabled
cacheChan = make(chan struct{})
rpcWait = append(rpcWait, cacheChan)
go cacheData(ratingDb, accountDb, cacheChan)
}
if cfg.RaterEnabled && cfg.RaterBalancer != "" && !cfg.BalancerEnabled {
go registerToBalancer()
go stopRaterSignalHandler()
stopHandled = true
}
responder := &engine.Responder{ExitChan: exitChan}
apier := &apier.ApierV1{StorDb: loadDb, RatingDb: ratingDb, AccountDb: accountDb, CdrDb: cdrDb, LogDb: logDb, Config: cfg}
if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterBalancer != utils.INTERNAL {
engine.Logger.Info("Registering Rater service")
server.RpcRegister(responder)
server.RpcRegister(apier)
}
if cfg.BalancerEnabled {
engine.Logger.Info("Registering Balancer service.")
go stopBalancerSignalHandler()
stopHandled = true
responder.Bal = bal
server.RpcRegister(responder)
server.RpcRegister(apier)
if cfg.RaterEnabled {
engine.Logger.Info("<Balancer> Registering internal rater")
bal.AddClient("local", new(engine.ResponderWorker))
}
}
if !stopHandled {
go generalSignalHandler()
}
if cfg.SchedulerEnabled {
engine.Logger.Info("Starting CGRateS Scheduler.")
go func() {
sched := scheduler.NewScheduler()
go reloadSchedulerSingnalHandler(sched, accountDb)
apier.Sched = sched
sched.LoadActionTimings(accountDb)
sched.Loop()
}()
}
var histServChan chan struct{} // Will be initialized only if the server starts
if cfg.HistoryServerEnabled {
histServChan = make(chan struct{})
rpcWait = append(rpcWait, histServChan)
go startHistoryServer(histServChan)
}
if cfg.HistoryAgentEnabled {
engine.Logger.Info("Starting CGRateS History Agent.")
go startHistoryAgent(scribeServer, histServChan)
}
var medChan chan struct{}
if cfg.MediatorEnabled {
engine.Logger.Info("Starting CGRateS Mediator service.")
medChan = make(chan struct{})
go startMediator(responder, logDb, cdrDb, cacheChan, medChan)
}
var cdrsChan chan struct{}
if cfg.CDRSEnabled {
engine.Logger.Info("Starting CGRateS CDRS service.")
cdrsChan = make(chan struct{})
httpWait = append(httpWait, cdrsChan)
go startCDRS(responder, cdrDb, medChan, cdrsChan)
}
if cfg.SMEnabled {
engine.Logger.Info("Starting CGRateS SessionManager service.")
go startSessionManager(responder, logDb, cacheChan)
// close all sessions on shutdown
go shutdownSessionmanagerSingnalHandler()
}
if cfg.CdrcEnabled {
engine.Logger.Info("Starting CGRateS CDR client.")
go startCdrc(cdrsChan)
}
// Start the servers
go serveRpc(rpcWait)
go serveHttp(httpWait)
<-exitChan
if *pidFile != "" {
if err := os.Remove(*pidFile); err != nil {
engine.Logger.Warning("Could not remove pid file: " + err.Error())
}
}
engine.Logger.Info("Stopped all components. CGRateS shutdown!")
}

View File

@@ -20,36 +20,46 @@ package main
import (
"fmt"
"github.com/cgrates/cgrates/rater"
"github.com/cgrates/cgrates/scheduler"
"net/rpc"
"os"
"os/signal"
"syscall"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/scheduler"
)
/*
Listens for SIGTERM, SIGINT, SIGQUIT system signals and shuts down all the registered raters.
Listens for SIGTERM, SIGINT, SIGQUIT system signals and shuts down all the registered engines.
*/
func stopBalancerSingnalHandler() {
func stopBalancerSignalHandler() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
sig := <-c
rater.Logger.Info(fmt.Sprintf("Caught signal %v, sending shutdownto raters\n", sig))
engine.Logger.Info(fmt.Sprintf("Caught signal %v, sending shutdown to engines\n", sig))
bal.Shutdown("Responder.Shutdown")
exitChan <- true
}
func generalSignalHandler() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
sig := <-c
engine.Logger.Info(fmt.Sprintf("Caught signal %v, shuting down cgr-engine\n", sig))
exitChan <- true
}
/*
Listens for the SIGTERM, SIGINT, SIGQUIT system signals and gracefuly unregister from balancer and closes the storage before exiting.
*/
func stopRaterSingnalHandler() {
func stopRaterSignalHandler() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
sig := <-c
rater.Logger.Info(fmt.Sprintf("Caught signal %v, unregistering from balancer\n", sig))
engine.Logger.Info(fmt.Sprintf("Caught signal %v, unregistering from balancer\n", sig))
unregisterFromBalancer()
exitChan <- true
}
@@ -60,47 +70,47 @@ Connects to the balancer and calls unregister RPC method.
func unregisterFromBalancer() {
client, err := rpc.Dial("tcp", cfg.RaterBalancer)
if err != nil {
rater.Logger.Crit("Cannot contact the balancer!")
engine.Logger.Crit("Cannot contact the balancer!")
exitChan <- true
return
}
var reply int
rater.Logger.Info(fmt.Sprintf("Unregistering from balancer %s", cfg.RaterBalancer))
client.Call("Responder.UnRegisterRater", cfg.RaterListen, &reply)
engine.Logger.Info(fmt.Sprintf("Unregistering from balancer %s", cfg.RaterBalancer))
client.Call("Responder.UnRegisterRater", cfg.RPCGOBListen, &reply)
if err := client.Close(); err != nil {
rater.Logger.Crit("Could not close balancer unregistration!")
engine.Logger.Crit("Could not close balancer unregistration!")
exitChan <- true
}
}
/*
Connects to the balancer and rehisters the rater to the server.
Connects to the balancer and rehisters the engine to the server.
*/
func registerToBalancer() {
client, err := rpc.Dial("tcp", cfg.RaterBalancer)
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Cannot contact the balancer: %v", err))
engine.Logger.Crit(fmt.Sprintf("Cannot contact the balancer: %v", err))
exitChan <- true
return
}
var reply int
rater.Logger.Info(fmt.Sprintf("Registering to balancer %s", cfg.RaterBalancer))
client.Call("Responder.RegisterRater", cfg.RaterListen, &reply)
engine.Logger.Info(fmt.Sprintf("Registering to balancer %s", cfg.RaterBalancer))
client.Call("Responder.RegisterRater", cfg.RPCGOBListen, &reply)
if err := client.Close(); err != nil {
rater.Logger.Crit("Could not close balancer registration!")
engine.Logger.Crit("Could not close balancer registration!")
exitChan <- true
}
rater.Logger.Info("Registration finished!")
engine.Logger.Info("Registration finished!")
}
// Listens for the HUP system signal and gracefuly reloads the timers from database.
func reloadSchedulerSingnalHandler(sched *scheduler.Scheduler, getter rater.DataStorage) {
func reloadSchedulerSingnalHandler(sched *scheduler.Scheduler, getter engine.AccountingStorage) {
for {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGHUP)
sig := <-c
rater.Logger.Info(fmt.Sprintf("Caught signal %v, reloading action timings.\n", sig))
engine.Logger.Info(fmt.Sprintf("Caught signal %v, reloading action timings.\n", sig))
sched.LoadActionTimings(getter)
// check the tip of the queue for new actions
sched.Restart()
@@ -116,7 +126,7 @@ func shutdownSessionmanagerSingnalHandler() {
<-c
if err := sm.Shutdown(); err != nil {
rater.Logger.Warning(fmt.Sprintf("<SessionManager> %s", err))
engine.Logger.Warning(fmt.Sprintf("<SessionManager> %s", err))
}
exitChan <- true
}

View File

@@ -21,155 +21,191 @@ package main
import (
"flag"
"fmt"
"github.com/cgrates/cgrates/rater"
"log"
"net/rpc"
"path"
"regexp"
"strconv"
)
const (
POSTGRES = "postgres"
MONGO = "mongo"
REDIS = "redis"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/history"
"github.com/cgrates/cgrates/utils"
)
var (
//separator = flag.String("separator", ",", "Default field separator")
db_type = flag.String("dbtype", REDIS, "The type of the database (redis|mongo|postgres)")
db_host = flag.String("dbhost", "localhost", "The database host to connect to.")
db_port = flag.String("dbport", "6379", "The database port to bind to.")
db_name = flag.String("dbname", "10", "he name/number of the database to connect to.")
db_user = flag.String("dbuser", "", "The database user to sign in as.")
db_pass = flag.String("dbpass", "", "The database user's password.")
cgrConfig, _ = config.NewDefaultCGRConfig()
ratingdb_type = flag.String("ratingdb_type", cgrConfig.RatingDBType, "The type of the RatingDb database <redis>")
ratingdb_host = flag.String("ratingdb_host", cgrConfig.RatingDBHost, "The RatingDb host to connect to.")
ratingdb_port = flag.String("ratingdb_port", cgrConfig.RatingDBPort, "The RatingDb port to bind to.")
ratingdb_name = flag.String("ratingdb_name", cgrConfig.RatingDBName, "The name/number of the RatingDb to connect to.")
ratingdb_user = flag.String("ratingdb_user", cgrConfig.RatingDBUser, "The RatingDb user to sign in as.")
ratingdb_pass = flag.String("ratingdb_passwd", cgrConfig.RatingDBPass, "The RatingDb user's password.")
flush = flag.Bool("flush", false, "Flush the database before importing")
dataPath = flag.String("path", ".", "The path containing the data files")
version = flag.Bool("version", false, "Prints the application version.")
accountdb_type = flag.String("accountdb_type", cgrConfig.AccountDBType, "The type of the AccountingDb database <redis>")
accountdb_host = flag.String("accountdb_host", cgrConfig.AccountDBHost, "The AccountingDb host to connect to.")
accountdb_port = flag.String("accountdb_port", cgrConfig.AccountDBPort, "The AccountingDb port to bind to.")
accountdb_name = flag.String("accountdb_name", cgrConfig.AccountDBName, "The name/number of the AccountingDb to connect to.")
accountdb_user = flag.String("accountdb_user", cgrConfig.AccountDBUser, "The AccountingDb user to sign in as.")
accountdb_pass = flag.String("accountdb_passwd", cgrConfig.AccountDBPass, "The AccountingDb user's password.")
destinationsFn = "Destinations.csv"
ratesFn = "Rates.csv"
timingsFn = "Timings.csv"
ratetimingsFn = "RateTimings.csv"
ratingprofilesFn = "RatingProfiles.csv"
actionsFn = "Actions.csv"
actiontimingsFn = "ActionTimings.csv"
actiontriggersFn = "ActionTriggers.csv"
accountactionsFn = "AccountActions.csv"
sep rune
stor_db_type = flag.String("stordb_type", cgrConfig.StorDBType, "The type of the storDb database <mysql>")
stor_db_host = flag.String("stordb_host", cgrConfig.StorDBHost, "The storDb host to connect to.")
stor_db_port = flag.String("stordb_port", cgrConfig.StorDBPort, "The storDb port to bind to.")
stor_db_name = flag.String("stordb_name", cgrConfig.StorDBName, "The name/number of the storDb to connect to.")
stor_db_user = flag.String("stordb_user", cgrConfig.StorDBUser, "The storDb user to sign in as.")
stor_db_pass = flag.String("stordb_passwd", cgrConfig.StorDBPass, "The storDb user's password.")
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")
)
type validator struct {
fn string
re *regexp.Regexp
message string
}
func main() {
flag.Parse()
if *version {
fmt.Println("CGRateS " + rater.VERSION)
fmt.Println("CGRateS " + utils.VERSION)
return
}
dataFilesValidators := []*validator{
&validator{destinationsFn,
regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\d+.?\d*){1}$`),
"Tag[0-9A-Za-z_],Prefix[0-9]"},
&validator{ratesFn,
regexp.MustCompile(`(?:\w+\s*,\s*){2}(?:\d+.?\d*,?){4}$`),
"Tag[0-9A-Za-z_],DestinationsTag[0-9A-Za-z_],ConnectFee[0-9.],Price[0-9.],PricedUnits[0-9.],RateIncrement[0-9.]"},
&validator{timingsFn,
regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\*all\s*,\s*|(?:\d{1,4};?)+\s*,\s*|\s*,\s*){4}(?:\d{2}:\d{2}:\d{2}|\*asap){1}$`),
"Tag[0-9A-Za-z_],Years[0-9;]|*all|<empty>,Months[0-9;]|*all|<empty>,MonthDays[0-9;]|*all|<empty>,WeekDays[0-9;]|*all|<empty>,Time[0-9:]|*asap(00:00:00)"},
&validator{ratetimingsFn,
regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+.?\d*){1}$`),
"Tag[0-9A-Za-z_],RatesTag[0-9A-Za-z_],TimingProfile[0-9A-Za-z_],Weight[0-9.]"},
&validator{ratingprofilesFn,
regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:\d+\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\*all\s*,\s*|[\w:\.]+\s*,\s*){1}(?:\w*\s*,\s*){1}(?:\w+\s*,\s*){1}(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z){1}$`),
"Tenant[0-9A-Za-z_],TOR[0-9],Direction OUT|IN,Subject[0-9A-Za-z_:.]|*all,RatesFallbackSubject[0-9A-Za-z_]|<empty>,RatesTimingTag[0-9A-Za-z_],ActivationTime[[0-9T:X]] (2012-01-01T00:00:00Z)"},
&validator{actionsFn,
regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\d+\s*,\s*){1}(?:\w+\s*,\s*|\*all\s*,\s*){1}(?:ABSOLUTE\s*,\s*|PERCENT\s*,\s*|\s*,\s*){1}(?:\d*\.?\d*\s*,?\s*){3}$`),
"Tag[0-9A-Za-z_],Action[0-9A-Za-z_],BalanceTag[0-9A-Za-z_],Direction OUT|IN,Units[0-9],DestinationTag[0-9A-Za-z_]|*all,PriceType ABSOLUT|PERCENT,PriceValue[0-9.],MinutesWeight[0-9.],Weight[0-9.]"},
&validator{actiontimingsFn,
regexp.MustCompile(`(?:\w+\s*,\s*){3}(?:\d+\.?\d*){1}`),
"Tag[0-9A-Za-z_],ActionsTag[0-9A-Za-z_],TimingTag[0-9A-Za-z_],Weight[0-9.]"},
&validator{actiontriggersFn,
regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:MONETARY\s*,\s*|SMS\s*,\s*|MINUTES\s*,\s*|INTERNET\s*,\s*|INTERNET_TIME\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\d+\.?\d*\s*,\s*){1}(?:\w+\s*,\s*|\*all\s*,\s*){1}(?:\w+\s*,\s*){1}(?:\d+\.?\d*){1}$`),
"Tag[0-9A-Za-z_],BalanceTag MONETARY|SMS|MINUTES|INTERNET|INTERNET_TIME,Direction OUT|IN,ThresholdValue[0-9.],DestinationTag[0-9A-Za-z_]|*all,ActionsTag[0-9A-Za-z_],Weight[0-9.]"},
&validator{accountactionsFn,
regexp.MustCompile(`(?:\w+\s*,\s*){1}(?:[\w:.]+\s*,\s*){1}(?:OUT\s*,\s*|IN\s*,\s*){1}(?:\w+\s*,?\s*){2}$`),
"Tenant[0-9A-Za-z_],Account[0-9A-Za-z_:.],Direction OUT|IN,ActionTimingsTag[0-9A-Za-z_],ActionTriggersTag[0-9A-Za-z_]"},
var errRatingDb, errAccDb, errStorDb, err error
var ratingDb engine.RatingStorage
var accountDb engine.AccountingStorage
var storDb engine.LoadStorage
var rater *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
if *fromStorDb {
ratingDb, errRatingDb = engine.ConfigureRatingStorage(*ratingdb_type, *ratingdb_host, *ratingdb_port, *ratingdb_name,
*ratingdb_user, *ratingdb_pass, *dbdata_encoding)
accountDb, errAccDb = engine.ConfigureAccountingStorage(*accountdb_type, *accountdb_host, *accountdb_port, *accountdb_name, *accountdb_user, *accountdb_pass, *dbdata_encoding)
storDb, errStorDb = engine.ConfigureLoadStorage(*stor_db_type, *stor_db_host, *stor_db_port, *stor_db_name, *stor_db_user, *stor_db_pass, *dbdata_encoding)
} else if *toStorDb { // Import from csv files to storDb
storDb, errStorDb = engine.ConfigureLoadStorage(*stor_db_type, *stor_db_host, *stor_db_port, *stor_db_name, *stor_db_user, *stor_db_pass, *dbdata_encoding)
} else { // Default load from csv files to dataDb
ratingDb, errRatingDb = engine.ConfigureRatingStorage(*ratingdb_type, *ratingdb_host, *ratingdb_port, *ratingdb_name,
*ratingdb_user, *ratingdb_pass, *dbdata_encoding)
accountDb, errAccDb = engine.ConfigureAccountingStorage(*accountdb_type, *accountdb_host, *accountdb_port, *accountdb_name, *accountdb_user, *accountdb_pass, *dbdata_encoding)
}
// Defer databases opened to be closed when we are done
for _, db := range []engine.Storage{ratingDb, accountDb, storDb} {
if db != nil {
defer db.Close()
}
}
// Stop on db errors
for _, err = range []error{errRatingDb, errAccDb, errStorDb} {
if err != nil {
log.Fatalf("Could not open database connection: %v", err)
}
}
if *toStorDb { // Import files from a directory into storDb
if *tpid == "" {
log.Fatal("TPid required, please define it via *-tpid* command argument.")
}
csvImporter := engine.TPCSVImporter{*tpid, storDb, *dataPath, ',', *verbose, *runId}
if errImport := csvImporter.Run(); errImport != nil {
log.Fatal(errImport)
}
return
}
}
for _, v := range dataFilesValidators {
err := rater.ValidateCSVData(path.Join(*dataPath, v.fn), v.re)
if *fromStorDb { // Load Tariff Plan from storDb into dataDb
loader = engine.NewDbReader(storDb, ratingDb, accountDb, *tpid)
} else { // Default load from csv files to dataDb
for fn, v := range engine.FileValidators {
err := engine.ValidateCSVData(path.Join(*dataPath, fn), v.Rule)
if err != nil {
log.Fatal(err, "\n\t", v.Message)
}
}
loader = engine.NewFileCSVReader(ratingDb, accountDb, ',',
path.Join(*dataPath, utils.DESTINATIONS_CSV),
path.Join(*dataPath, utils.TIMINGS_CSV),
path.Join(*dataPath, utils.RATES_CSV),
path.Join(*dataPath, utils.DESTINATION_RATES_CSV),
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.ACTIONS_CSV),
path.Join(*dataPath, utils.ACTION_PLANS_CSV),
path.Join(*dataPath, utils.ACTION_TRIGGERS_CSV),
path.Join(*dataPath, utils.ACCOUNT_ACTIONS_CSV))
}
err = loader.LoadAll()
if err != nil {
log.Fatal(err)
}
if *stats {
loader.ShowStatistics()
}
if *dryRun { // We were just asked to parse the data, not saving it
return
}
if *historyServer != "" { // Init scribeAgent so we can store the differences
if scribeAgent, err := history.NewProxyScribe(*historyServer); err != nil {
log.Fatalf("Could not connect to history server, error: %s. Make sure you have properly configured it via -history_server flag.", err.Error())
return
} else {
engine.SetHistoryScribe(scribeAgent)
defer scribeAgent.Client.Close()
}
} else {
log.Print("WARNING: Rates history archiving is disabled!")
}
if *raterAddress != "" { // Init connection to rater so we can reload it's data
rater, err = rpc.Dial("tcp", *raterAddress)
if err != nil {
log.Fatal(err, "\n\t", v.message)
log.Fatalf("Could not connect to rater: %s", err.Error())
return
}
}
//sep = []rune(*separator)[0]
sep = ','
csvr := rater.NewFileCSVReader()
err := csvr.LoadDestinations(path.Join(*dataPath, destinationsFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadRates(path.Join(*dataPath, ratesFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadTimings(path.Join(*dataPath, timingsFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadRateTimings(path.Join(*dataPath, ratetimingsFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadRatingProfiles(path.Join(*dataPath, ratingprofilesFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadActions(path.Join(*dataPath, actionsFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadActionTimings(path.Join(*dataPath, actiontimingsFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadActionTriggers(path.Join(*dataPath, actiontriggersFn), sep)
if err != nil {
log.Fatal(err)
}
err = csvr.LoadAccountActions(path.Join(*dataPath, accountactionsFn), sep)
if err != nil {
log.Fatal(err)
}
var getter rater.DataStorage
switch *db_type {
case REDIS:
db_nb, err := strconv.Atoi(*db_name)
if err != nil {
log.Fatal("Redis db name must be an integer!")
}
if *db_port != "" {
*db_host += ":" + *db_port
}
getter, err = rater.NewGosexyStorage(*db_host, db_nb, *db_pass)
case MONGO:
getter, err = rater.NewMongoStorage(*db_host, *db_port, *db_name, *db_user, *db_pass)
case POSTGRES:
getter, err = rater.NewPostgresStorage(*db_host, *db_port, *db_name, *db_user, *db_pass)
default:
log.Fatal("Unknown data db type, exiting!")
}
if err != nil {
log.Fatalf("Could not open database connection: %v", err)
} else {
log.Print("WARNING: Rates automatic cache reloading is disabled!")
}
// write maps to database
if err := csvr.WriteToDatabase(getter, *flush, true); err != nil {
if err := loader.WriteToDatabase(*flush, *verbose); err != nil {
log.Fatal("Could not write to database: ", err)
}
if len(*historyServer) != 0 && *verbose {
log.Print("Wrote history.")
}
// Reload scheduler and cache
if rater != nil {
reply := ""
dstIds, _ := loader.GetLoadedIds(engine.DESTINATION_PREFIX)
rplIds, _ := loader.GetLoadedIds(engine.RATING_PLAN_PREFIX)
rpfIds, _ := loader.GetLoadedIds(engine.RATING_PROFILE_PREFIX)
actIds, _ := loader.GetLoadedIds(engine.ACTION_PREFIX)
shgIds, _ := loader.GetLoadedIds(engine.SHARED_GROUP_PREFIX)
rpAliases, _ := loader.GetLoadedIds(engine.RP_ALIAS_PREFIX)
accAliases, _ := loader.GetLoadedIds(engine.ACC_ALIAS_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())
}
actTmgIds, _ := loader.GetLoadedIds(engine.ACTION_TIMING_PREFIX)
if len(actTmgIds) != 0 {
if *verbose {
log.Print("Reloading scheduler")
}
if err = rater.Call("ApierV1.ReloadScheduler", "", &reply); err != nil {
log.Fatalf("Got error on scheduler reload: %s", err.Error())
}
}
}
}

View File

@@ -1,359 +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 main
import (
"errors"
"flag"
"fmt"
"github.com/cgrates/cgrates/balancer2go"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/mediator"
"github.com/cgrates/cgrates/rater"
"github.com/cgrates/cgrates/scheduler"
"github.com/cgrates/cgrates/sessionmanager"
"io"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"os"
"runtime"
"strconv"
"strings"
"time"
)
const (
DISABLED = "disabled"
INTERNAL = "internal"
JSON = "json"
GOB = "gob"
POSTGRES = "postgres"
MONGO = "mongo"
REDIS = "redis"
SAME = "same"
FS = "freeswitch"
)
var (
cfgPath = flag.String("config", "/etc/cgrates/cgrates.cfg", "Configuration file location.")
version = flag.Bool("version", false, "Prints the application version.")
bal = balancer2go.NewBalancer()
exitChan = make(chan bool)
sm sessionmanager.SessionManager
cfg *config.CGRConfig
err error
)
func listenToRPCRequests(rpcResponder interface{}, rpcAddress string, rpc_encoding string) {
l, err := net.Listen("tcp", rpcAddress)
if err != nil {
rater.Logger.Crit(fmt.Sprintf("<Rater> Could not listen to %v: %v", rpcAddress, err))
exitChan <- true
return
}
defer l.Close()
rater.Logger.Info(fmt.Sprintf("<Rater> Listening for incomming RPC requests on %v", l.Addr()))
rpc.Register(rpcResponder)
var serveFunc func(io.ReadWriteCloser)
if rpc_encoding == JSON {
serveFunc = jsonrpc.ServeConn
} else {
serveFunc = rpc.ServeConn
}
for {
conn, err := l.Accept()
if err != nil {
rater.Logger.Err(fmt.Sprintf("<Rater> Accept error: %v", conn))
continue
}
rater.Logger.Info(fmt.Sprintf("<Rater> New incoming connection: %v", conn.RemoteAddr()))
go serveFunc(conn)
}
}
func startMediator(responder *rater.Responder, loggerDb rater.DataStorage) {
var connector rater.Connector
if cfg.MediatorRater == INTERNAL {
connector = responder
} else {
var client *rpc.Client
var err error
if cfg.MediatorRPCEncoding == JSON {
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
client, err = jsonrpc.Dial("tcp", cfg.MediatorRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
} else {
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.MediatorRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
}
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Could not connect to rater: %v", err))
exitChan <- true
}
connector = &rater.RPCClientConnector{Client: client}
}
if _, err := os.Stat(cfg.MediatorCDRInDir); err != nil {
rater.Logger.Crit(fmt.Sprintf("The input path for mediator does not exist: %v", cfg.MediatorCDRInDir))
exitChan <- true
}
if _, err := os.Stat(cfg.MediatorCDROutDir); err != nil {
rater.Logger.Crit(fmt.Sprintf("The output path for mediator does not exist: %v", cfg.MediatorCDROutDir))
exitChan <- true
}
// ToDo: Why is here
// Check parsing errors
//if cfgParseErr != nil {
// rater.Logger.Crit(fmt.Sprintf("Errors on config parsing: <%v>", cfgParseErr))
// exitChan <- true
//}
m, err := mediator.NewMediator(connector, loggerDb, cfg.MediatorSkipDB, cfg.MediatorCDROutDir, cfg.MediatorPseudoprepaid,
cfg.FreeswitchDirectionIdx, cfg.FreeswitchTORIdx, cfg.FreeswitchTenantIdx, cfg.FreeswitchSubjectIdx, cfg.FreeswitchAccountIdx,
cfg.FreeswitchDestIdx, cfg.FreeswitchTimeStartIdx, cfg.FreeswitchDurationIdx, cfg.FreeswitchUUIDIdx)
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Mediator config parsing error: %v", err))
exitChan <- true
}
m.TrackCDRFiles(cfg.MediatorCDRInDir)
}
func startSessionManager(responder *rater.Responder, loggerDb rater.DataStorage) {
var connector rater.Connector
if cfg.SMRater == INTERNAL {
connector = responder
} else {
var client *rpc.Client
var err error
if cfg.SMRPCEncoding == JSON {
// We attempt to reconnect more times
for i := 0; i < cfg.SMRaterReconnects; i++ {
client, err = jsonrpc.Dial("tcp", cfg.SMRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
} else {
for i := 0; i < cfg.SMRaterReconnects; i++ {
client, err = rpc.Dial("tcp", cfg.SMRater)
if err == nil { //Connected so no need to reiterate
break
}
time.Sleep(time.Duration(i/2) * time.Second)
}
}
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Could not connect to rater: %v", err))
exitChan <- true
}
connector = &rater.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 {
rater.Logger.Err(fmt.Sprintf("<SessionManager> error: %s!", errConn))
}
default:
rater.Logger.Err(fmt.Sprintf("<SessionManager> Unsupported session manger type: %s!", cfg.SMSwitchType))
exitChan <- true
}
exitChan <- true
}
func checkConfigSanity() error {
if cfg.SMEnabled && cfg.RaterEnabled && cfg.RaterBalancer != DISABLED {
rater.Logger.Crit("The session manager must not be enabled on a worker rater (change [rater]/balancer to disabled)!")
return errors.New("SessionManager on Worker")
}
if cfg.BalancerEnabled && cfg.RaterEnabled && cfg.RaterBalancer != DISABLED {
rater.Logger.Crit("The balancer is enabled so it cannot connect to anatoher balancer (change [rater]/balancer to disabled)!")
return errors.New("Improperly configured balancer")
}
// check if the session manager or mediator is connectting via loopback
// if they are using the same encoding as the rater/balancer
// this scenariou should be used for debug puropses only (it is racy anyway)
// and it might be forbidden in the future
if strings.Contains(cfg.SMRater, "localhost") || strings.Contains(cfg.SMRater, "127.0.0.1") {
if cfg.BalancerEnabled {
if cfg.BalancerRPCEncoding != cfg.SMRPCEncoding {
rater.Logger.Crit("If you are connecting the session manager via the loopback to the balancer use the same type of rpc encoding!")
return errors.New("Balancer and SessionManager using different encoding")
}
}
if cfg.RaterEnabled {
if cfg.RaterRPCEncoding != cfg.SMRPCEncoding {
rater.Logger.Crit("If you are connecting the session manager via the loopback to the arter use the same type of rpc encoding!")
return errors.New("Rater and SessionManager using different encoding")
}
}
}
if strings.Contains(cfg.MediatorRater, "localhost") || strings.Contains(cfg.MediatorRater, "127.0.0.1") {
if cfg.BalancerEnabled {
if cfg.BalancerRPCEncoding != cfg.MediatorRPCEncoding {
rater.Logger.Crit("If you are connecting the mediator via the loopback to the balancer use the same type of rpc encoding!")
return errors.New("Balancer and Mediator using different encoding")
}
}
if cfg.RaterEnabled {
if cfg.RaterRPCEncoding != cfg.MediatorRPCEncoding {
rater.Logger.Crit("If you are connecting the mediator via the loopback to the arter use the same type of rpc encoding!")
return errors.New("Rater and Mediator using different encoding")
}
}
}
return nil
}
func configureDatabase(db_type, host, port, name, user, pass string) (getter rater.DataStorage, err error) {
switch db_type {
case REDIS:
var db_nb int
db_nb, err = strconv.Atoi(name)
if err != nil {
rater.Logger.Crit("Redis db name must be an integer!")
return nil, err
}
if port != "" {
host += ":" + port
}
getter, err = rater.NewGosexyStorage(host, db_nb, pass)
case MONGO:
getter, err = rater.NewMongoStorage(host, port, name, user, pass)
case POSTGRES:
getter, err = rater.NewPostgresStorage(host, port, name, user, pass)
default:
err = errors.New("unknown db")
return nil, err
}
if err != nil {
return nil, err
}
return getter, nil
}
func main() {
flag.Parse()
if *version {
fmt.Println("CGRateS " + rater.VERSION)
return
}
runtime.GOMAXPROCS(runtime.NumCPU())
cfg, err = config.NewCGRConfig(cfgPath)
if err != nil {
rater.Logger.Crit(fmt.Sprintf("Could not parse config: %s exiting!", err))
return
}
// some consitency checks
errCfg := checkConfigSanity()
if errCfg != nil {
rater.Logger.Crit(errCfg.Error())
return
}
var getter, loggerDb rater.DataStorage
getter, err = configureDatabase(cfg.DataDBType, cfg.DataDBHost, cfg.DataDBPort, cfg.DataDBName, cfg.DataDBUser, cfg.DataDBPass)
if err != nil { // Cannot configure getter database, show stopper
rater.Logger.Crit(fmt.Sprintf("Could not configure database: %s exiting!", err))
return
}
defer getter.Close()
rater.SetDataStorage(getter)
if cfg.LogDBType == SAME {
loggerDb = getter
} else {
loggerDb, err = configureDatabase(cfg.LogDBType, cfg.LogDBHost, cfg.LogDBPort, cfg.LogDBName, cfg.LogDBUser, cfg.LogDBPass)
if err != nil { // Cannot configure logger database, show stopper
rater.Logger.Crit(fmt.Sprintf("Could not configure logger database: %s exiting!", err))
return
}
}
defer loggerDb.Close()
rater.SetStorageLogger(loggerDb)
if cfg.SMDebitInterval > 0 {
if dp, err := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval)); err == nil {
rater.SetDebitPeriod(dp)
}
}
// Async starts here
if cfg.RaterEnabled && cfg.RaterBalancer != DISABLED && !cfg.BalancerEnabled {
go registerToBalancer()
go stopRaterSingnalHandler()
}
responder := &rater.Responder{ExitChan: exitChan}
if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterListen != INTERNAL {
rater.Logger.Info(fmt.Sprintf("Starting CGRateS Rater on %s.", cfg.RaterListen))
go listenToRPCRequests(responder, cfg.RaterListen, cfg.RaterRPCEncoding)
}
if cfg.BalancerEnabled {
rater.Logger.Info(fmt.Sprintf("Starting CGRateS Balancer on %s.", cfg.BalancerListen))
go stopBalancerSingnalHandler()
responder.Bal = bal
go listenToRPCRequests(responder, cfg.BalancerListen, cfg.BalancerRPCEncoding)
if cfg.RaterEnabled {
rater.Logger.Info("Starting internal rater.")
bal.AddClient("local", new(rater.ResponderWorker))
}
}
if cfg.SchedulerEnabled {
rater.Logger.Info("Starting CGRateS Scheduler.")
go func() {
sched := scheduler.NewScheduler()
go reloadSchedulerSingnalHandler(sched, getter)
sched.LoadActionTimings(getter)
sched.Loop()
}()
}
if cfg.SMEnabled {
rater.Logger.Info("Starting CGRateS SessionManager.")
go startSessionManager(responder, loggerDb)
// close all sessions on shutdown
go shutdownSessionmanagerSingnalHandler()
}
if cfg.MediatorEnabled {
rater.Logger.Info("Starting CGRateS Mediator.")
go startMediator(responder, loggerDb)
}
<-exitChan
rater.Logger.Info("Stopped all components. CGRateS shutdown!")
}

3
cmd/cgr-tester/README.md Normal file
View File

@@ -0,0 +1,3 @@
* Stress testing tools
All stress testing tools work with ../../data/tariffplans/fs_germany_prep1/. So start a rater and than issue a `cgr-loader -flushdb -verbose` in that directory.

View File

@@ -0,0 +1,174 @@
/*
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 main
import (
"flag"
"fmt"
"log"
"net/rpc"
"os"
"runtime"
"runtime/pprof"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
)
var (
cgrConfig, _ = config.NewDefaultCGRConfig()
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
memprofile = flag.String("memprofile", "", "write memory profile to this file")
runs = flag.Int("runs", 10000, "stress cycle number")
parallel = flag.Int("parallel", 0, "run n requests in parallel")
ratingdb_type = flag.String("ratingdb_type", cgrConfig.RatingDBType, "The type of the RatingDb database <redis>")
ratingdb_host = flag.String("ratingdb_host", cgrConfig.RatingDBHost, "The RatingDb host to connect to.")
ratingdb_port = flag.String("ratingdb_port", cgrConfig.RatingDBPort, "The RatingDb port to bind to.")
ratingdb_name = flag.String("ratingdb_name", cgrConfig.RatingDBName, "The name/number of the RatingDb to connect to.")
ratingdb_user = flag.String("ratingdb_user", cgrConfig.RatingDBUser, "The RatingDb user to sign in as.")
ratingdb_pass = flag.String("ratingdb_passwd", cgrConfig.RatingDBPass, "The RatingDb user's password.")
accountdb_type = flag.String("accountdb_type", cgrConfig.AccountDBType, "The type of the AccountingDb database <redis>")
accountdb_host = flag.String("accountdb_host", cgrConfig.AccountDBHost, "The AccountingDb host to connect to.")
accountdb_port = flag.String("accountdb_port", cgrConfig.AccountDBPort, "The AccountingDb port to bind to.")
accountdb_name = flag.String("accountdb_name", cgrConfig.AccountDBName, "The name/number of the AccountingDb to connect to.")
accountdb_user = flag.String("accountdb_user", cgrConfig.AccountDBUser, "The AccountingDb user to sign in as.")
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.")
subject = flag.String("subject", "1001", "The rating subject to use in queries.")
destination = flag.String("destination", "+4986517174963", "The destination to use in queries.")
nilDuration = time.Duration(0)
)
func durInternalRater(cd *engine.CallDescriptor) (time.Duration, error) {
ratingDb, err := engine.ConfigureRatingStorage(*ratingdb_type, *ratingdb_host, *ratingdb_port, *ratingdb_name, *ratingdb_user, *ratingdb_pass, *dbdata_encoding)
if err != nil {
return nilDuration, fmt.Errorf("Could not connect to rating database: %s", err.Error())
}
defer ratingDb.Close()
engine.SetRatingStorage(ratingDb)
accountDb, err := engine.ConfigureAccountingStorage(*accountdb_type, *accountdb_host, *accountdb_port, *accountdb_name, *accountdb_user, *accountdb_pass, *dbdata_encoding)
if err != nil {
return nilDuration, fmt.Errorf("Could not connect to accounting database: %s", err.Error())
}
defer accountDb.Close()
engine.SetAccountingStorage(accountDb)
if err := ratingDb.CacheRating(nil, nil, nil, nil); err != nil {
return nilDuration, fmt.Errorf("Cache rating error: %s", err.Error())
}
log.Printf("Runnning %d cycles...", *runs)
var result *engine.CallCost
j := 0
start := time.Now()
for i := 0; i < *runs; i++ {
result, err = cd.GetCost()
if *memprofile != "" {
runtime.MemProfileRate = 1
runtime.GC()
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
break
}
j = i
}
log.Print(result, j, err)
memstats := new(runtime.MemStats)
runtime.ReadMemStats(memstats)
log.Printf("memstats before GC: Kbytes = %d footprint = %d",
memstats.HeapAlloc/1024, memstats.Sys/1024)
return time.Since(start), nil
}
func durRemoteRater(cd *engine.CallDescriptor) (time.Duration, error) {
result := engine.CallCost{}
client, err := rpc.Dial("tcp", *raterAddress)
if err != nil {
return nilDuration, fmt.Errorf("Could not connect to engine: ", err.Error())
}
defer client.Close()
start := time.Now()
if *parallel > 0 {
// var divCall *rpc.Call
var sem = make(chan int, *parallel)
var finish = make(chan int)
for i := 0; i < *runs; i++ {
go func() {
sem <- 1
client.Call("Responder.GetCost", cd, &result)
<-sem
finish <- 1
// divCall = client.Go("Responder.GetCost", cd, &result, nil)
}()
}
for i := 0; i < *runs; i++ {
<-finish
}
// <-divCall.Done
} else {
for j := 0; j < *runs; j++ {
client.Call("Responder.GetCost", cd, &result)
}
}
log.Println(result)
return time.Since(start), nil
}
func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU() - 1)
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
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,
}
var duration time.Duration
var err error
if len(*raterAddress) == 0 {
duration, err = durInternalRater(cd)
} else {
duration, err = durRemoteRater(cd)
}
if err != nil {
log.Fatal(err.Error())
} else {
log.Printf("Elapsed: %d resulted: %f req/s.", duration, float64(*runs)/duration.Seconds())
}
}

View File

@@ -1,84 +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 main
import (
"flag"
"github.com/cgrates/cgrates/rater"
"log"
"net/rpc"
//"net/rpc/jsonrpc"
"net/rpc/jsonrpc"
"runtime"
"time"
)
var (
balancer = flag.String("balancer", "localhost:2001", "balancer server address")
runs = flag.Int("runs", 10000, "stress cycle number")
parallel = flag.Int("parallel", 0, "run n requests in parallel")
json = flag.Bool("json", false, "use JSON for RPC encoding")
)
func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU())
t1 := time.Date(2012, time.February, 02, 17, 30, 0, 0, time.UTC)
t2 := time.Date(2012, time.February, 02, 18, 30, 0, 0, time.UTC)
cd := rater.CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
result := rater.CallCost{}
var client *rpc.Client
var err error
if *json {
client, err = jsonrpc.Dial("tcp", *balancer)
} else {
client, err = rpc.Dial("tcp", *balancer)
}
if err != nil {
log.Fatal("Could not connect to rater: ", err)
}
start := time.Now()
if *parallel > 0 {
// var divCall *rpc.Call
var sem = make(chan int, *parallel)
var finish = make(chan int)
for i := 0; i < *runs; i++ {
go func() {
sem <- 1
client.Call("Responder.GetCost", cd, &result)
<-sem
finish <- 1
// divCall = client.Go("Responder.GetCost", cd, &result, nil)
}()
}
for i := 0; i < *runs; i++ {
<-finish
}
// <-divCall.Done
} else {
for j := 0; j < *runs; j++ {
client.Call("Responder.GetCost", cd, &result)
}
}
duration := time.Since(start)
log.Println(result)
client.Close()
log.Printf("Elapsed: %v resulted: %v req/s.", duration, float64(*runs)/duration.Seconds())
}

View File

@@ -1,83 +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 main
import (
"flag"
"github.com/cgrates/cgrates/rater"
"log"
"os"
"runtime"
"runtime/pprof"
"time"
)
var (
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
memprofile = flag.String("memprofile", "", "write memory profile to this file")
runs = flag.Int("runs", 10000, "stress cycle number")
)
func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU() - 1)
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
return
}
t1 := time.Date(2012, time.February, 02, 17, 30, 0, 0, time.UTC)
t2 := time.Date(2012, time.February, 02, 18, 30, 0, 0, time.UTC)
cd := rater.CallDescriptor{Direction: "OUT", TOR: "0", Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
getter, err := rater.NewGosexyStorage("localhost:6379", 10, "")
//getter, err := rater.NewMongoStorage("localhost", "cgrates")
defer getter.Close()
rater.SetDataStorage(getter)
log.Printf("Runnning %d cycles...", *runs)
var result *rater.CallCost
j := 0
start := time.Now()
for i := 0; i < *runs; i++ {
result, err = cd.GetCost()
j = i
}
duration := time.Since(start)
log.Print(result, j, err)
memstats := new(runtime.MemStats)
runtime.ReadMemStats(memstats)
log.Printf("memstats before GC: Kbytes = %d footprint = %d",
memstats.HeapAlloc/1024, memstats.Sys/1024)
log.Printf("Elapsed: %v resulted: %v req/s.", duration, float64(*runs)/duration.Seconds())
}

View File

@@ -19,14 +19,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package config
import (
"code.google.com/p/goconf/conf"
"errors"
"fmt"
"os"
"strings"
"time"
"code.google.com/p/goconf/conf"
"github.com/cgrates/cgrates/utils"
)
const (
DISABLED = "disabled"
INTERNAL = "internal"
JSON = "json"
GOB = "gob"
POSTGRES = "postgres"
@@ -36,59 +40,292 @@ const (
FS = "freeswitch"
)
var cgrCfg *CGRConfig // will be shared
// Used to retrieve system configuration from other packages
func CgrConfig() *CGRConfig {
return cgrCfg
}
// Used to set system configuration from other places
func SetCgrConfig(cfg *CGRConfig) {
cgrCfg = cfg
}
// Holds system configuration, defaults are overwritten with values from config file if found
type CGRConfig struct {
DataDBType string
DataDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
DataDBPort string // The port to bind to.
DataDBName string // The name of the database to connect to.
DataDBUser string // The user to sign in as.
DataDBPass string // The user's password.
LogDBType string // Should reflect the database type used to store logs
LogDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
LogDBPort string // The port to bind to.
LogDBName string // The name of the database to connect to.
LogDBUser string // The user to sign in as.
LogDBPass string // The user's password.
RaterEnabled bool // start standalone server (no balancer)
RaterBalancer string // balancer address host:port
RaterListen string // listening address host:port
RaterRPCEncoding string // use JSON for RPC encoding
BalancerEnabled bool
BalancerListen string // Json RPC server address
BalancerRPCEncoding string // use JSON for RPC encoding
SchedulerEnabled bool
SMEnabled bool
SMSwitchType string
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
SMRaterReconnects int // Number of reconnect attempts to rater
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
SMRPCEncoding string // use JSON for RPC encoding
SMDefaultReqType string // Use this request type if not defined on top
SMDefaultTOR string // set default type of record
SMDefaultTenant string // set default tenant
SMDefaultSubject string // set default rating subject, useful in case of fallback
MediatorEnabled bool
MediatorCDRType string // sets the type of cdrs we are processing.
MediatorCDRInDir string // Freeswitch Master CSV CDR path.
MediatorCDROutDir string // Freeswitch Master CSV CDR output path.
MediatorRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
MediatorRaterReconnects int // Number of reconnect attempts to rater
MediatorRPCEncoding string // use JSON for RPC encoding
MediatorSkipDB bool
MediatorPseudoprepaid bool
FreeswitchServer string // freeswitch address host:port
FreeswitchPass string // FS socket password
FreeswitchDirectionIdx string
FreeswitchTORIdx string
FreeswitchTenantIdx string
FreeswitchSubjectIdx string
FreeswitchAccountIdx string
FreeswitchDestIdx string
FreeswitchTimeStartIdx string
FreeswitchDurationIdx string
FreeswitchUUIDIdx string
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
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
}
func (self *CGRConfig) setDefaults() error {
self.RatingDBType = REDIS
self.RatingDBHost = "127.0.0.1"
self.RatingDBPort = "6379"
self.RatingDBName = "10"
self.RatingDBUser = ""
self.RatingDBPass = ""
self.AccountDBType = REDIS
self.AccountDBHost = "127.0.0.1"
self.AccountDBPort = "6379"
self.AccountDBName = "11"
self.AccountDBUser = ""
self.AccountDBPass = ""
self.StorDBType = utils.MYSQL
self.StorDBHost = "localhost"
self.StorDBPort = "3306"
self.StorDBName = "cgrates"
self.StorDBUser = "cgrates"
self.StorDBPass = "CGRateS.org"
self.DBDataEncoding = utils.MSGPACK
self.RPCJSONListen = "127.0.0.1:2012"
self.RPCGOBListen = "127.0.0.1:2013"
self.HTTPListen = "127.0.0.1:2080"
self.DefaultReqType = utils.RATED
self.DefaultTOR = "call"
self.DefaultTenant = "cgrates.org"
self.DefaultSubject = "cgrates"
self.RoundingMethod = utils.ROUNDING_MIDDLE
self.RoundingDecimals = 4
self.XmlCfgDocument = nil
self.RaterEnabled = false
self.RaterBalancer = ""
self.BalancerEnabled = false
self.SchedulerEnabled = false
self.CDRSEnabled = false
self.CDRSExtraFields = []*utils.RSRField{}
self.CDRSMediator = ""
self.CdreCdrFormat = "csv"
self.CdreDir = "/var/log/cgrates/cdr/cdrexport/csv"
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.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.SMEnabled = false
self.SMSwitchType = FS
self.SMRater = "internal"
self.SMRaterReconnects = 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.FreeswitchServer = "127.0.0.1:8021"
self.FreeswitchPass = "ClueCon"
self.FreeswitchReconnects = 5
self.HistoryAgentEnabled = false
self.HistoryServerEnabled = false
self.HistoryServer = "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},
}
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")
}
}
// 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
}
func NewDefaultCGRConfig() (*CGRConfig, error) {
cfg := &CGRConfig{}
cfg.setDefaults()
if err := cfg.checkConfigSanity(); err != nil {
return nil, err
}
return cfg, nil
}
// Instantiate a new CGRConfig setting defaults or reading from file
@@ -97,7 +334,14 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
return loadConfig(c)
cfg, err := loadConfig(c)
if err != nil {
return nil, err
}
if err := cfg.checkConfigSanity(); err != nil {
return nil, err
}
return cfg, nil
}
func NewCGRConfigBytes(data []byte) (*CGRConfig, error) {
@@ -105,216 +349,409 @@ func NewCGRConfigBytes(data []byte) (*CGRConfig, error) {
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
}
return loadConfig(c)
cfg, err := loadConfig(c)
if err != nil {
return nil, err
}
if err := cfg.checkConfigSanity(); err != nil {
return nil, err
}
return cfg, nil
}
func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
cfg := &CGRConfig{}
cfg.setDefaults()
var hasOpt bool
cfg.DataDBType = REDIS
if hasOpt = c.HasOption("global", "datadb_type"); hasOpt {
cfg.DataDBType, _ = c.GetString("global", "datadb_type")
var errParse error
if hasOpt = c.HasOption("global", "ratingdb_type"); hasOpt {
cfg.RatingDBType, _ = c.GetString("global", "ratingdb_type")
}
cfg.DataDBHost = "127.0.0.1"
if hasOpt = c.HasOption("global", "datadb_host"); hasOpt {
cfg.DataDBHost, _ = c.GetString("global", "datadb_host")
if hasOpt = c.HasOption("global", "ratingdb_host"); hasOpt {
cfg.RatingDBHost, _ = c.GetString("global", "ratingdb_host")
}
cfg.DataDBPort = "6379"
if hasOpt = c.HasOption("global", "datadb_port"); hasOpt {
cfg.DataDBPort, _ = c.GetString("global", "datadb_port")
if hasOpt = c.HasOption("global", "ratingdb_port"); hasOpt {
cfg.RatingDBPort, _ = c.GetString("global", "ratingdb_port")
}
cfg.DataDBName = "10"
if hasOpt = c.HasOption("global", "datadb_name"); hasOpt {
cfg.DataDBName, _ = c.GetString("global", "datadb_name")
if hasOpt = c.HasOption("global", "ratingdb_name"); hasOpt {
cfg.RatingDBName, _ = c.GetString("global", "ratingdb_name")
}
cfg.DataDBUser = ""
if hasOpt = c.HasOption("global", "datadb_user"); hasOpt {
cfg.DataDBUser, _ = c.GetString("global", "datadb_user")
if hasOpt = c.HasOption("global", "ratingdb_user"); hasOpt {
cfg.RatingDBUser, _ = c.GetString("global", "ratingdb_user")
}
cfg.DataDBPass = ""
if hasOpt = c.HasOption("global", "datadb_passwd"); hasOpt {
cfg.DataDBPass, _ = c.GetString("global", "datadb_passwd")
if hasOpt = c.HasOption("global", "ratingdb_passwd"); hasOpt {
cfg.RatingDBPass, _ = c.GetString("global", "ratingdb_passwd")
}
cfg.LogDBType = MONGO
if hasOpt = c.HasOption("global", "logdb_type"); hasOpt {
cfg.LogDBType, _ = c.GetString("global", "logdb_type")
if hasOpt = c.HasOption("global", "accountdb_type"); hasOpt {
cfg.AccountDBType, _ = c.GetString("global", "accountdb_type")
}
cfg.LogDBHost = "localhost"
if hasOpt = c.HasOption("global", "logdb_host"); hasOpt {
cfg.LogDBHost, _ = c.GetString("global", "logdb_host")
if hasOpt = c.HasOption("global", "accountdb_host"); hasOpt {
cfg.AccountDBHost, _ = c.GetString("global", "accountdb_host")
}
cfg.LogDBPort = "27017"
if hasOpt = c.HasOption("global", "logdb_port"); hasOpt {
cfg.LogDBPort, _ = c.GetString("global", "logdb_port")
if hasOpt = c.HasOption("global", "accountdb_port"); hasOpt {
cfg.AccountDBPort, _ = c.GetString("global", "accountdb_port")
}
cfg.LogDBName = "cgrates"
if hasOpt = c.HasOption("global", "logdb_name"); hasOpt {
cfg.LogDBName, _ = c.GetString("global", "logdb_name")
if hasOpt = c.HasOption("global", "accountdb_name"); hasOpt {
cfg.AccountDBName, _ = c.GetString("global", "accountdb_name")
}
cfg.LogDBUser = ""
if hasOpt = c.HasOption("global", "logdb_user"); hasOpt {
cfg.LogDBUser, _ = c.GetString("global", "logdb_user")
if hasOpt = c.HasOption("global", "accountdb_user"); hasOpt {
cfg.AccountDBUser, _ = c.GetString("global", "accountdb_user")
}
cfg.LogDBPass = ""
if hasOpt = c.HasOption("global", "logdb_passwd"); hasOpt {
cfg.LogDBPass, _ = c.GetString("global", "logdb_passwd")
if hasOpt = c.HasOption("global", "accountdb_passwd"); hasOpt {
cfg.AccountDBPass, _ = c.GetString("global", "accountdb_passwd")
}
if hasOpt = c.HasOption("global", "stordb_type"); hasOpt {
cfg.StorDBType, _ = c.GetString("global", "stordb_type")
}
if hasOpt = c.HasOption("global", "stordb_host"); hasOpt {
cfg.StorDBHost, _ = c.GetString("global", "stordb_host")
}
if hasOpt = c.HasOption("global", "stordb_port"); hasOpt {
cfg.StorDBPort, _ = c.GetString("global", "stordb_port")
}
if hasOpt = c.HasOption("global", "stordb_name"); hasOpt {
cfg.StorDBName, _ = c.GetString("global", "stordb_name")
}
if hasOpt = c.HasOption("global", "stordb_user"); hasOpt {
cfg.StorDBUser, _ = c.GetString("global", "stordb_user")
}
if hasOpt = c.HasOption("global", "stordb_passwd"); hasOpt {
cfg.StorDBPass, _ = c.GetString("global", "stordb_passwd")
}
if hasOpt = c.HasOption("global", "dbdata_encoding"); hasOpt {
cfg.DBDataEncoding, _ = c.GetString("global", "dbdata_encoding")
}
if hasOpt = c.HasOption("global", "rpc_json_listen"); hasOpt {
cfg.RPCJSONListen, _ = c.GetString("global", "rpc_json_listen")
}
if hasOpt = c.HasOption("global", "rpc_gob_listen"); hasOpt {
cfg.RPCGOBListen, _ = c.GetString("global", "rpc_gob_listen")
}
if hasOpt = c.HasOption("global", "http_listen"); hasOpt {
cfg.HTTPListen, _ = c.GetString("global", "http_listen")
}
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_tenant"); hasOpt {
cfg.DefaultTenant, _ = c.GetString("global", "default_tenant")
}
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")
}
// XML config path defined, try loading the document
if hasOpt = c.HasOption("global", "xmlcfg_path"); hasOpt {
xmlCfgPath, _ := c.GetString("global", "xmlcfg_path")
xmlFile, err := os.Open(xmlCfgPath)
if err != nil {
return nil, err
}
if cgrXmlCfgDoc, err := ParseCgrXmlConfig(xmlFile); err != nil {
return nil, err
} else {
cfg.XmlCfgDocument = cgrXmlCfgDoc
}
}
cfg.RaterEnabled = false
if hasOpt = c.HasOption("rater", "enabled"); hasOpt {
cfg.RaterEnabled, _ = c.GetBool("rater", "enabled")
}
cfg.RaterBalancer = DISABLED
if hasOpt = c.HasOption("rater", "balancer"); hasOpt {
cfg.RaterBalancer, _ = c.GetString("rater", "balancer")
}
cfg.RaterListen = "127.0.0.1:2012"
if hasOpt = c.HasOption("rater", "listen"); hasOpt {
cfg.RaterListen, _ = c.GetString("rater", "listen")
}
cfg.RaterRPCEncoding = GOB
if hasOpt = c.HasOption("rater", "rpc_encoding"); hasOpt {
cfg.RaterRPCEncoding, _ = c.GetString("rater", "rpc_encoding")
}
cfg.BalancerEnabled = false
if hasOpt = c.HasOption("balancer", "enabled"); hasOpt {
cfg.BalancerEnabled, _ = c.GetBool("balancer", "enabled")
}
cfg.BalancerListen = "127.0.0.1:2013"
if hasOpt = c.HasOption("balancer", "listen"); hasOpt {
cfg.BalancerListen, _ = c.GetString("balancer", "listen")
}
cfg.BalancerRPCEncoding = GOB
if hasOpt = c.HasOption("balancer", "rpc_encoding"); hasOpt {
cfg.BalancerRPCEncoding, _ = c.GetString("balancer", "rpc_encoding")
}
cfg.SchedulerEnabled = false
if hasOpt = c.HasOption("scheduler", "enabled"); hasOpt {
cfg.SchedulerEnabled, _ = c.GetBool("scheduler", "enabled")
}
cfg.MediatorEnabled = false
if hasOpt = c.HasOption("cdrs", "enabled"); hasOpt {
cfg.CDRSEnabled, _ = c.GetBool("cdrs", "enabled")
}
if hasOpt = c.HasOption("cdrs", "extra_fields"); hasOpt {
extraFieldsStr, _ := c.GetString("cdrs", "extra_fields")
if extraFields, err := ParseRSRFields(extraFieldsStr); err != nil {
return nil, errParse
} else {
cfg.CDRSExtraFields = extraFields
}
}
if hasOpt = c.HasOption("cdrs", "mediator"); hasOpt {
cfg.CDRSMediator, _ = c.GetString("cdrs", "mediator")
}
if hasOpt = c.HasOption("cdre", "cdr_format"); hasOpt {
cfg.CdreCdrFormat, _ = c.GetString("cdre", "cdr_format")
}
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
}
} else if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
if xmlTemplate, err := cfg.XmlCfgDocument.GetCdreFWCfg(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil {
return nil, err
} else {
cfg.CdreFWXmlTemplate = xmlTemplate
}
}
}
if hasOpt = c.HasOption("cdre", "export_dir"); hasOpt {
cfg.CdreDir, _ = c.GetString("cdre", "export_dir")
}
if hasOpt = c.HasOption("cdrc", "enabled"); hasOpt {
cfg.CdrcEnabled, _ = c.GetBool("cdrc", "enabled")
}
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 hasOpt = c.HasOption("cdrc", "cdr_type"); hasOpt {
cfg.CdrcCdrType, _ = c.GetString("cdrc", "cdr_type")
}
if hasOpt = c.HasOption("cdrc", "cdr_in_dir"); hasOpt {
cfg.CdrcCdrInDir, _ = c.GetString("cdrc", "cdr_in_dir")
}
if hasOpt = c.HasOption("cdrc", "cdr_out_dir"); hasOpt {
cfg.CdrcCdrOutDir, _ = c.GetString("cdrc", "cdr_out_dir")
}
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
}
}
if hasOpt = c.HasOption("mediator", "enabled"); hasOpt {
cfg.MediatorEnabled, _ = c.GetBool("mediator", "enabled")
}
cfg.MediatorCDRInDir = "/var/log/freeswitch/cdr-csv"
if hasOpt = c.HasOption("mediator", "cdr_in_dir"); hasOpt {
cfg.MediatorCDRInDir, _ = c.GetString("mediator", "cdr_in_dir")
}
cfg.MediatorCDROutDir = "/var/log/cgrates/cdr_out"
if hasOpt = c.HasOption("mediator", "cdr_out_dir"); hasOpt {
cfg.MediatorCDROutDir, _ = c.GetString("mediator", "cdr_out_dir")
}
cfg.MediatorRater = "127.0.0.1:2012"
if hasOpt = c.HasOption("mediator", "rater"); hasOpt {
cfg.MediatorRater, _ = c.GetString("mediator", "rater")
}
cfg.MediatorRaterReconnects = 3
if hasOpt = c.HasOption("mediator", "rater_reconnects"); hasOpt {
cfg.MediatorRaterReconnects, _ = c.GetInt("mediator", "rater_reconnects")
}
cfg.MediatorRPCEncoding = GOB
if hasOpt = c.HasOption("mediator", "rpc_encoding"); hasOpt {
cfg.MediatorRPCEncoding, _ = c.GetString("mediator", "rpc_encoding")
if hasOpt = c.HasOption("mediator", "run_ids"); hasOpt {
if cfg.MediatorRunIds, errParse = ConfigSlice(c, "mediator", "run_ids"); errParse != nil {
return nil, errParse
}
}
cfg.MediatorSkipDB = false
if hasOpt = c.HasOption("mediator", "skipdb"); hasOpt {
cfg.MediatorSkipDB, _ = c.GetBool("mediator", "skipdb")
if hasOpt = c.HasOption("mediator", "subject_fields"); hasOpt {
if cfg.MediatorSubjectFields, errParse = ConfigSlice(c, "mediator", "subject_fields"); errParse != nil {
return nil, errParse
}
}
cfg.MediatorPseudoprepaid = false
if hasOpt = c.HasOption("mediator", "pseudoprepaid"); hasOpt {
cfg.MediatorPseudoprepaid, _ = c.GetBool("mediator", "pseudoprepaid")
if hasOpt = c.HasOption("mediator", "reqtype_fields"); hasOpt {
if cfg.MediatorReqTypeFields, errParse = ConfigSlice(c, "mediator", "reqtype_fields"); errParse != nil {
return nil, errParse
}
}
cfg.MediatorCDRType = "freeswitch_csv"
if hasOpt = c.HasOption("mediator", "cdr_type"); hasOpt {
cfg.MediatorCDRType, _ = c.GetString("mediator", "cdr_type")
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
}
}
cfg.SMEnabled = false
if hasOpt = c.HasOption("session_manager", "enabled"); hasOpt {
cfg.SMEnabled, _ = c.GetBool("session_manager", "enabled")
}
cfg.SMSwitchType = FS
if hasOpt = c.HasOption("session_manager", "switch_type"); hasOpt {
cfg.SMSwitchType, _ = c.GetString("session_manager", "switch_type")
}
cfg.SMRater = "127.0.0.1:2012"
if hasOpt = c.HasOption("session_manager", "rater"); hasOpt {
cfg.SMRater, _ = c.GetString("session_manager", "rater")
}
cfg.SMRaterReconnects = 3
if hasOpt = c.HasOption("session_manager", "rater_reconnects"); hasOpt {
cfg.SMRaterReconnects, _ = c.GetInt("session_manager", "rater_reconnects")
}
cfg.SMDebitInterval = 10
if hasOpt = c.HasOption("session_manager", "debit_interval"); hasOpt {
cfg.SMDebitInterval, _ = c.GetInt("session_manager", "debit_interval")
}
cfg.SMRPCEncoding = GOB
if hasOpt = c.HasOption("session_manager", "rpc_encoding"); hasOpt {
cfg.SMRPCEncoding, _ = c.GetString("session_manager", "rpc_encoding")
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
}
}
cfg.SMDefaultReqType = "" // By default CGRateS is inactive, customer should activate when he feels he is ready
if hasOpt = c.HasOption("session_manager", "default_reqtype"); hasOpt {
cfg.SMDefaultReqType, _ = c.GetString("session_manager", "default_reqtype")
if hasOpt = c.HasOption("session_manager", "run_ids"); hasOpt {
if cfg.SMRunIds, errParse = ConfigSlice(c, "session_manager", "run_ids"); errParse != nil {
return nil, errParse
}
}
cfg.SMDefaultTOR = "0"
if hasOpt = c.HasOption("session_manager", "default_tor"); hasOpt {
cfg.SMDefaultTOR, _ = c.GetString("session_manager", "default_tor")
if hasOpt = c.HasOption("session_manager", "reqtype_fields"); hasOpt {
if cfg.SMReqTypeFields, errParse = ConfigSlice(c, "session_manager", "reqtype_fields"); errParse != nil {
return nil, errParse
}
}
cfg.SMDefaultTenant = "0"
if hasOpt = c.HasOption("session_manager", "default_tenant"); hasOpt {
cfg.SMDefaultTenant, _ = c.GetString("session_manager", "default_tenant")
if hasOpt = c.HasOption("session_manager", "direction_fields"); hasOpt {
if cfg.SMDirectionFields, errParse = ConfigSlice(c, "session_manager", "direction_fields"); errParse != nil {
return nil, errParse
}
}
cfg.SMDefaultSubject = "0"
if hasOpt = c.HasOption("session_manager", "default_subject"); hasOpt {
cfg.SMDefaultSubject, _ = c.GetString("session_manager", "default_subject")
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
}
}
cfg.FreeswitchServer = "127.0.0.1:8021"
if hasOpt = c.HasOption("freeswitch", "server"); hasOpt {
cfg.FreeswitchServer, _ = c.GetString("freeswitch", "server")
}
cfg.FreeswitchPass = "ClueCon"
if hasOpt = c.HasOption("freeswitch", "passwd"); hasOpt {
cfg.FreeswitchPass, _ = c.GetString("freeswitch", "passwd")
}
cfg.FreeswitchReconnects = 5
if hasOpt = c.HasOption("freeswitch", "reconnects"); hasOpt {
cfg.FreeswitchReconnects, _ = c.GetInt("freeswitch", "reconnects")
}
cfg.FreeswitchUUIDIdx = "10"
if hasOpt = c.HasOption("freeswitch", "uuid_index"); hasOpt {
cfg.FreeswitchUUIDIdx, _ = c.GetString("freeswitch", "uuid_index")
if hasOpt = c.HasOption("history_agent", "enabled"); hasOpt {
cfg.HistoryAgentEnabled, _ = c.GetBool("history_agent", "enabled")
}
cfg.FreeswitchTORIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "tor_index"); hasOpt {
cfg.FreeswitchTORIdx, _ = c.GetString("freeswitch", "tor_index")
if hasOpt = c.HasOption("history_agent", "server"); hasOpt {
cfg.HistoryServer, _ = c.GetString("history_agent", "server")
}
cfg.FreeswitchTenantIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "tenant_index"); hasOpt {
cfg.FreeswitchTenantIdx, _ = c.GetString("freeswitch", "tenant_index")
if hasOpt = c.HasOption("history_server", "enabled"); hasOpt {
cfg.HistoryServerEnabled, _ = c.GetBool("history_server", "enabled")
}
cfg.FreeswitchDirectionIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "direction_index"); hasOpt {
cfg.FreeswitchDirectionIdx, _ = c.GetString("freeswitch", "direction_index")
if hasOpt = c.HasOption("history_server", "history_dir"); hasOpt {
cfg.HistoryDir, _ = c.GetString("history_server", "history_dir")
}
cfg.FreeswitchSubjectIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "subject_index"); hasOpt {
cfg.FreeswitchSubjectIdx, _ = c.GetString("freeswitch", "subject_index")
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
}
}
cfg.FreeswitchAccountIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "account_index"); hasOpt {
cfg.FreeswitchAccountIdx, _ = c.GetString("freeswitch", "account_index")
if hasOpt = c.HasOption("mailer", "server"); hasOpt {
cfg.MailerServer, _ = c.GetString("mailer", "server")
}
cfg.FreeswitchDestIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "destination_index"); hasOpt {
cfg.FreeswitchDestIdx, _ = c.GetString("freeswitch", "destination_index")
if hasOpt = c.HasOption("mailer", "auth_user"); hasOpt {
cfg.MailerAuthUser, _ = c.GetString("mailer", "auth_user")
}
cfg.FreeswitchTimeStartIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "time_start_index"); hasOpt {
cfg.FreeswitchTimeStartIdx, _ = c.GetString("freeswitch", "time_start_index")
if hasOpt = c.HasOption("mailer", "auth_passwd"); hasOpt {
cfg.MailerAuthPass, _ = c.GetString("mailer", "auth_passwd")
}
cfg.FreeswitchDurationIdx = "-1"
if hasOpt = c.HasOption("freeswitch", "duration_index"); hasOpt {
cfg.FreeswitchDurationIdx, _ = c.GetString("freeswitch", "duration_index")
if hasOpt = c.HasOption("mailer", "from_address"); hasOpt {
cfg.MailerFromAddr, _ = c.GetString("mailer", "from_address")
}
return cfg, nil
}

View File

@@ -0,0 +1,47 @@
/*
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 (
"flag"
"path"
"testing"
)
var testLocal = flag.Bool("local", false, "Perform the tests only on local test environment, not by default.") // This flag will be passed here via "go test -local" args
var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here")
func TestLoadXmlCfg(t *testing.T) {
if !*testLocal {
return
}
cfgPath := path.Join(*dataDir, "conf", "samples", "config_local_test.cfg")
cfg, err := NewCGRConfig(&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 {
t.Error("Could not retrieve CDRExporter FixedWidth config instance")
}
}

View File

@@ -20,116 +20,290 @@ package config
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/cgrates/cgrates/utils"
)
func TestConfig(t *testing.T) {
cfgPth := "test_data.txt"
cfg, err := NewCGRConfig(&cfgPth)
if err != nil {
t.Log(fmt.Sprintf("Could not parse config: %s!", err))
t.FailNow()
func TestConfigSharing(t *testing.T) {
cfg, _ := NewDefaultCGRConfig()
SetCgrConfig(cfg)
cfgReturn := CgrConfig()
if !reflect.DeepEqual(cfgReturn, cfg) {
t.Errorf("Retrieved %v, Expected %v", cfgReturn, cfg)
}
if cfg.DataDBType != "test" ||
cfg.DataDBHost != "test" ||
cfg.DataDBPort != "test" ||
cfg.DataDBName != "test" ||
cfg.DataDBUser != "test" ||
cfg.DataDBPass != "test" ||
cfg.LogDBType != "test" ||
cfg.LogDBHost != "test" ||
cfg.LogDBPort != "test" ||
cfg.LogDBName != "test" ||
cfg.LogDBUser != "test" ||
cfg.LogDBPass != "test" ||
cfg.RaterEnabled != true ||
cfg.RaterBalancer != "test" ||
cfg.RaterListen != "test" ||
cfg.RaterRPCEncoding != "test" ||
cfg.BalancerEnabled != true ||
cfg.BalancerListen != "test" ||
cfg.BalancerRPCEncoding != "test" ||
cfg.SchedulerEnabled != true ||
cfg.SMEnabled != true ||
cfg.SMSwitchType != "test" ||
cfg.SMRater != "test" ||
cfg.SMDebitInterval != 11 ||
cfg.SMRPCEncoding != "test" ||
cfg.MediatorEnabled != true ||
cfg.MediatorCDRInDir != "test" ||
cfg.MediatorCDROutDir != "test" ||
cfg.MediatorRater != "test" ||
cfg.MediatorRPCEncoding != "test" ||
cfg.MediatorSkipDB != true ||
cfg.MediatorPseudoprepaid != true ||
cfg.FreeswitchServer != "test" ||
cfg.FreeswitchPass != "test" ||
cfg.FreeswitchDirectionIdx != "test" ||
cfg.FreeswitchTORIdx != "test" ||
cfg.FreeswitchTenantIdx != "test" ||
cfg.FreeswitchSubjectIdx != "test" ||
cfg.FreeswitchAccountIdx != "test" ||
cfg.FreeswitchDestIdx != "test" ||
cfg.FreeswitchTimeStartIdx != "test" ||
cfg.FreeswitchDurationIdx != "test" ||
cfg.FreeswitchUUIDIdx != "test" {
t.Log(cfg.DataDBType)
t.Log(cfg.DataDBHost)
t.Log(cfg.DataDBPort)
t.Log(cfg.DataDBName)
t.Log(cfg.DataDBUser)
t.Log(cfg.DataDBPass)
t.Log(cfg.LogDBType)
t.Log(cfg.LogDBHost)
t.Log(cfg.LogDBPort)
t.Log(cfg.LogDBName)
t.Log(cfg.LogDBUser)
t.Log(cfg.LogDBPass)
t.Log(cfg.RaterEnabled)
t.Log(cfg.RaterBalancer)
t.Log(cfg.RaterListen)
t.Log(cfg.RaterRPCEncoding)
t.Log(cfg.BalancerEnabled)
t.Log(cfg.BalancerListen)
t.Log(cfg.BalancerRPCEncoding)
t.Log(cfg.SchedulerEnabled)
t.Log(cfg.SMEnabled)
t.Log(cfg.SMSwitchType)
t.Log(cfg.SMRater)
t.Log(cfg.SMDebitInterval)
t.Log(cfg.SMRPCEncoding)
t.Log(cfg.MediatorEnabled)
t.Log(cfg.MediatorCDRInDir)
t.Log(cfg.MediatorCDROutDir)
t.Log(cfg.MediatorRater)
t.Log(cfg.MediatorRPCEncoding)
t.Log(cfg.MediatorSkipDB)
t.Log(cfg.MediatorPseudoprepaid)
t.Log(cfg.FreeswitchServer)
t.Log(cfg.FreeswitchPass)
t.Log(cfg.FreeswitchDirectionIdx)
t.Log(cfg.FreeswitchTORIdx)
t.Log(cfg.FreeswitchTenantIdx)
t.Log(cfg.FreeswitchSubjectIdx)
t.Log(cfg.FreeswitchAccountIdx)
t.Log(cfg.FreeswitchDestIdx)
t.Log(cfg.FreeswitchTimeStartIdx)
t.Log(cfg.FreeswitchDurationIdx)
t.Log(cfg.FreeswitchUUIDIdx)
t.Error("Config file read failed!")
}
}
func TestParamOverwrite(t *testing.T) {
// Make sure defaults did not change by mistake
func TestDefaults(t *testing.T) {
cfg := &CGRConfig{}
errSet := cfg.setDefaults()
if errSet != nil {
t.Log(fmt.Sprintf("Coud not set defaults: %s!", errSet.Error()))
t.FailNow()
}
eCfg := &CGRConfig{}
eCfg.RatingDBType = REDIS
eCfg.RatingDBHost = "127.0.0.1"
eCfg.RatingDBPort = "6379"
eCfg.RatingDBName = "10"
eCfg.RatingDBUser = ""
eCfg.RatingDBPass = ""
eCfg.AccountDBType = REDIS
eCfg.AccountDBHost = "127.0.0.1"
eCfg.AccountDBPort = "6379"
eCfg.AccountDBName = "11"
eCfg.AccountDBUser = ""
eCfg.AccountDBPass = ""
eCfg.StorDBType = utils.MYSQL
eCfg.StorDBHost = "localhost"
eCfg.StorDBPort = "3306"
eCfg.StorDBName = "cgrates"
eCfg.StorDBUser = "cgrates"
eCfg.StorDBPass = "CGRateS.org"
eCfg.DBDataEncoding = utils.MSGPACK
eCfg.RPCJSONListen = "127.0.0.1:2012"
eCfg.RPCGOBListen = "127.0.0.1:2013"
eCfg.HTTPListen = "127.0.0.1:2080"
eCfg.DefaultReqType = utils.RATED
eCfg.DefaultTOR = "call"
eCfg.DefaultTenant = "cgrates.org"
eCfg.DefaultSubject = "cgrates"
eCfg.RoundingMethod = utils.ROUNDING_MIDDLE
eCfg.RoundingDecimals = 4
eCfg.XmlCfgDocument = nil
eCfg.RaterEnabled = false
eCfg.RaterBalancer = ""
eCfg.BalancerEnabled = false
eCfg.SchedulerEnabled = false
eCfg.CDRSEnabled = false
eCfg.CDRSExtraFields = []*utils.RSRField{}
eCfg.CDRSMediator = ""
eCfg.CdreCdrFormat = "csv"
eCfg.CdreDir = "/var/log/cgrates/cdr/cdrexport/csv"
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.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.SMEnabled = false
eCfg.SMSwitchType = FS
eCfg.SMRater = "internal"
eCfg.SMRaterReconnects = 3
eCfg.SMDebitInterval = 10
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.HistoryAgentEnabled = false
eCfg.HistoryServer = "internal"
eCfg.HistoryServerEnabled = false
eCfg.HistoryDir = "/var/log/cgrates/history"
eCfg.HistorySaveInterval = time.Duration(1) * time.Second
eCfg.MailerServer = "localhost:25"
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},
}
if !reflect.DeepEqual(cfg, eCfg) {
t.Log(eCfg)
t.Log(cfg)
t.Error("Defaults different than expected!")
}
}
func TestSanityCheck(t *testing.T) {
cfg := &CGRConfig{}
errSet := cfg.setDefaults()
if errSet != nil {
t.Error("Coud not set defaults: ", errSet.Error())
}
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
if err := cfg.checkConfigSanity(); err == nil {
t.Error("Failed to detect fixed_width dependency on xml configuration")
}
}
// Load config from file and make sure we have all set
func TestConfigFromFile(t *testing.T) {
cfgPth := "test_data.txt"
cfg, err := NewCGRConfig(&cfgPth)
if err != nil {
t.Log(fmt.Sprintf("Could not parse config: %s!", err))
t.FailNow()
}
if cfg.FreeswitchReconnects != 5 { // one default which is not overwritten in test data
t.Errorf("FreeswitchReconnects set == %d, expect 5", cfg.FreeswitchReconnects)
} else if cfg.SchedulerEnabled != true { // one parameter which should be overwritten in test data
t.Errorf("scheduler_enabled set == %d, expect true", cfg.SchedulerEnabled)
eCfg := &CGRConfig{} // Instance we expect to get out after reading config file
eCfg.setDefaults()
eCfg.RatingDBType = "test"
eCfg.RatingDBHost = "test"
eCfg.RatingDBPort = "test"
eCfg.RatingDBName = "test"
eCfg.RatingDBUser = "test"
eCfg.RatingDBPass = "test"
eCfg.AccountDBType = "test"
eCfg.AccountDBHost = "test"
eCfg.AccountDBPort = "test"
eCfg.AccountDBName = "test"
eCfg.AccountDBUser = "test"
eCfg.AccountDBPass = "test"
eCfg.StorDBType = "test"
eCfg.StorDBHost = "test"
eCfg.StorDBPort = "test"
eCfg.StorDBName = "test"
eCfg.StorDBUser = "test"
eCfg.StorDBPass = "test"
eCfg.DBDataEncoding = "test"
eCfg.RPCJSONListen = "test"
eCfg.RPCGOBListen = "test"
eCfg.HTTPListen = "test"
eCfg.DefaultReqType = "test"
eCfg.DefaultTOR = "test"
eCfg.DefaultTenant = "test"
eCfg.DefaultSubject = "test"
eCfg.RoundingMethod = "test"
eCfg.RoundingDecimals = 99
eCfg.RaterEnabled = true
eCfg.RaterBalancer = "test"
eCfg.BalancerEnabled = true
eCfg.SchedulerEnabled = true
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.CdrcEnabled = true
eCfg.CdrcCdrs = "test"
eCfg.CdrcCdrsMethod = "test"
eCfg.CdrcRunDelay = time.Duration(99) * time.Second
eCfg.CdrcCdrType = "test"
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.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.SMEnabled = true
eCfg.SMSwitchType = "test"
eCfg.SMRater = "test"
eCfg.SMRaterReconnects = 99
eCfg.SMDebitInterval = 99
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.HistoryAgentEnabled = true
eCfg.HistoryServer = "test"
eCfg.HistoryServerEnabled = true
eCfg.HistoryDir = "test"
eCfg.HistorySaveInterval = time.Duration(99) * time.Second
eCfg.MailerServer = "test"
eCfg.MailerAuthUser = "test"
eCfg.MailerAuthPass = "test"
eCfg.MailerFromAddr = "test"
if !reflect.DeepEqual(cfg, eCfg) {
t.Log(eCfg)
t.Log(cfg)
t.Error("Loading of configuration from file failed!")
}
}

66
config/helpers.go Normal file
View File

@@ -0,0 +1,66 @@
/*
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 (
"code.google.com/p/goconf/conf"
"errors"
"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")
}
}
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
}
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")
}
if rsrField, err := utils.NewRSRField(cfgValStr); err != nil {
return nil, err
} else {
rsrFields[idx] = rsrField
}
}
return rsrFields, nil
}

39
config/helpers_test.go Normal file
View File

@@ -0,0 +1,39 @@
/*
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 (
"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")
}
}

View File

@@ -1,60 +1,130 @@
### Test data, not for production usage
# TEST DATA - NOT FOR PRODUCTION USAGE
#
[global]
datadb_type = test #
datadb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_port = test # The port to bind to.
datadb_name = test # The name of the database to connect to.
datadb_user = test # The user to sign in as.
datadb_passwd = test # The user's password.root
logdb_type = test #
logdb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_port = test # The port to bind to.
logdb_name = test # The name of the database to connect to.
logdb_user = test # The user to sign in as.
logdb_passwd = test # The user's password.root
ratingdb_type = test # Rating subsystem database: <redis>.
ratingdb_host = test # Rating subsystem database host address.
ratingdb_port = test # Rating subsystem port to reach the database.
ratingdb_name = test # Rating subsystem database name to connect to.
ratingdb_user = test # Rating subsystem username to use when connecting to database.
ratingdb_passwd = test # Rating subsystem password to use when connecting to database.
accountdb_type = test # Accounting subsystem database: <redis>.
accountdb_host = test # Accounting subsystem database host address.
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_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>
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_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
[balancer]
enabled = true # Start balancer server
listen = test # Balancer listen interface
rpc_encoding = test # use JSON for RPC encoding
enabled = true # Start Balancer service: <true|false>.
[rater]
enabled = true
listen = test # listening address host:port, internal for internal communication only
balancer = test # if defined it will register to balancer as worker
rpc_encoding = test # use JSON for RPC encoding
[mediator]
enabled = true
cdr_in_dir = test # Freeswitch Master CSV CDR path.
cdr_out_dir = test
rater = test #address where to access rater. Can be internal, direct rater address or the address of a balancer
rpc_encoding = test # use JSON for RPC encoding
skipdb = true
pseudoprepaid = true
enabled = true # Enable Rater service: <true|false>.
balancer = test # Register to Balancer as worker: <enabled|disabled>.
[scheduler]
enabled = true
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
mediator = test # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
[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
[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>
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.
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.
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"
[mediator]
enabled = true # Starts Mediator 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.
[session_manager]
enabled = true
switch_type = test
rater = test #address where to access rater. Can be internal, direct rater address or the address of a balancer
debit_interval = 11
rpc_encoding = test # use JSON for RPC encoding
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.
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>.
[freeswitch]
server = test # freeswitch address host:port
passwd = test # freeswitch address host:port
direction_index = test
tor_index = test
tenant_index = test
subject_index = test
account_index = test
destination_index = test
time_start_index = test
duration_index = test
uuid_index = test
server = test # Adress where to connect to FreeSWITCH socket.
passwd = test # FreeSWITCH socket password.
reconnects = 99 # Number of attempts on connect failure.
[history_server]
enabled = true # Starts History service: <true|false>.
history_dir = test # Location on disk where to store history 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>
[mailer]
server = test # The server to use when sending emails out
auth_user = test # Authenticate to email server using this user
auth_passwd = test # Authenticate to email server with this password
from_address = test # From address used when sending emails out

123
config/xmlconfig.go Normal file
View File

@@ -0,0 +1,123 @@
/*
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 (
"encoding/xml"
"fmt"
"github.com/cgrates/cgrates/utils"
"io"
)
// Decodes a reader enforcing specific format of the configuration file
func ParseCgrXmlConfig(reader io.Reader) (*CgrXmlCfgDocument, error) {
xmlConfig := new(CgrXmlCfgDocument)
decoder := xml.NewDecoder(reader)
if err := decoder.Decode(xmlConfig); 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
}
// 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"`
}
// 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
}
}
}
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
}
}
if cfg, hasIt := xmlCfg.cdrefws[instName]; hasIt {
return cfg, nil
}
return nil, nil
}

119
config/xmlconfig_test.go Normal file
View File

@@ -0,0 +1,119 @@
/*
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))
}
}

82
console/add_account.go Normal file
View File

@@ -0,0 +1,82 @@
/*
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
}

97
console/add_balance.go Normal file
View File

@@ -0,0 +1,97 @@
/*
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

@@ -0,0 +1,90 @@
/*
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
}

View File

@@ -1,73 +0,0 @@
/* Implementing balance related console commands.
*/
package console
import (
"fmt"
"github.com/cgrates/cgrates/rater"
)
func init() {
commands["get_balance"] = &CmdGetBalance{}
}
// Commander implementation
type CmdGetBalance struct {
rpcMethod string
rpcParams *rater.CallDescriptor
rpcResult *rater.CallCost
}
// name should be exec's name
func (self *CmdGetBalance) Usage(name string) string {
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_balance <tenant> <user> [<balanceid> [<direction>]]")
}
// set param defaults
func (self *CmdGetBalance) defaults() error {
self.rpcMethod = "Responder.GetMonetary"
self.rpcParams = &rater.CallDescriptor{Direction: "OUT"}
return nil
}
// Parses command line args and builds CmdBalance value
func (self *CmdGetBalance) 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]
if len(args) > 4 {
switch args[4] {
case "MONETARY":
self.rpcMethod = "Responder.GetMonetary"
case "SMS":
self.rpcMethod = "Responder.GetSMS"
case "INETRNET":
self.rpcMethod = "Responder.GetInternet"
case "INTERNET_TIME":
self.rpcMethod = "Responder.GetInternetTime"
case "MINUTES":
self.rpcMethod = "Responder.GetMonetary"
}
}
if len(args) > 5 {
self.rpcParams.Direction = args[5]
}
return nil
}
func (self *CmdGetBalance) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetBalance) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetBalance) RpcResult() interface{} {
self.rpcResult = &rater.CallCost{}
return self.rpcResult
}

View File

@@ -1,7 +1,27 @@
/*
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 (
"errors"
"fmt"
"strings"
)
var (
@@ -25,7 +45,11 @@ func GetCommandValue(args []string) (Commander, error) {
}
cmdVal, exists := commands[args[1]]
if !exists {
return nil, errors.New("\n\tUsage: cgr-console [cfg_opts...{-h}] <status|get_balance>\n")
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, "|"))
}
if err := cmdVal.FromArgs(args); err != nil {
return nil, err

97
console/debit_balance.go Normal file
View File

@@ -0,0 +1,97 @@
/*
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
}

70
console/destination.go Normal file
View File

@@ -0,0 +1,70 @@
/*
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"
)
func init() {
commands["get_destination"] = &CmdGetDestination{}
}
// Commander implementation
type CmdGetDestination struct {
rpcMethod string
rpcParams string
rpcResult *engine.Destination
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetDestination) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetDestination) RpcResult() interface{} {
self.rpcResult = new(engine.Destination)
return self.rpcResult
}

75
console/execute_action.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 (
"fmt"
"github.com/cgrates/cgrates/apier"
)
func init() {
commands["execute_action"] = &CmdExecuteAction{}
}
// Commander implementation
type CmdExecuteAction struct {
rpcMethod string
rpcParams *apier.AttrExecuteAction
rpcResult string
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdExecuteAction) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdExecuteAction) RpcResult() interface{} {
return &self.rpcResult
}

92
console/export_cdrs.go Normal file
View File

@@ -0,0 +1,92 @@
/*
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
}

74
console/get_account.go Normal file
View File

@@ -0,0 +1,74 @@
/*
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"
)
func init() {
commands["get_account"] = &CmdGetAccount{}
}
// Commander implementation
type CmdGetAccount struct {
rpcMethod string
rpcParams *apier.AttrGetAccount
rpcResult *engine.Account
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetAccount) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetAccount) RpcResult() interface{} {
return &self.rpcResult
}

67
console/get_cache_age.go Normal file
View File

@@ -0,0 +1,67 @@
/*
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["get_cache_age"] = &CmdGetCacheAge{}
}
// Commander implementation
type CmdGetCacheAge struct {
rpcMethod string
rpcParams string
rpcResult *utils.CachedItemAge
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCacheAge) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetCacheAge) RpcResult() interface{} {
return &self.rpcResult
}

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 (
"fmt"
"github.com/cgrates/cgrates/utils"
)
func init() {
commands["get_cache_stats"] = &CmdGetCacheStats{}
}
// Commander implementation
type CmdGetCacheStats struct {
rpcMethod string
rpcParams *utils.AttrCacheStats
rpcResult utils.CacheStats
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdGetCacheStats) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdGetCacheStats) RpcResult() interface{} {
return &self.rpcResult
}

76
console/get_callcost.go Normal file
View File

@@ -0,0 +1,76 @@
/*
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
}

97
console/get_cost.go Normal file
View File

@@ -0,0 +1,97 @@
/*
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
}

102
console/get_maxduration.go Normal file
View File

@@ -0,0 +1,102 @@
/*
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/reload_cache.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 (
"fmt"
"github.com/cgrates/cgrates/utils"
)
func init() {
commands["reload_cache"] = &CmdReloadCache{}
}
// Commander implementation
type CmdReloadCache struct {
rpcMethod string
rpcParams *utils.ApiReloadCache
rpcResult string
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdReloadCache) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdReloadCache) RpcResult() interface{} {
return &self.rpcResult
}

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 (
"fmt"
)
func init() {
commands["reload_scheduler"] = &CmdReloadScheduler{}
}
// Commander implementation
type CmdReloadScheduler struct {
rpcMethod string
rpcParams string
rpcResult string
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdReloadScheduler) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdReloadScheduler) RpcResult() interface{} {
return &self.rpcResult
}

71
console/rem_cdrs.go Normal file
View File

@@ -0,0 +1,71 @@
/*
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["rem_cdrs"] = &CmdRemCdrs{}
}
// Commander implementation
type CmdRemCdrs struct {
rpcMethod string
rpcParams *utils.AttrRemCdrs
rpcResult string
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdRemCdrs) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdRemCdrs) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -0,0 +1,69 @@
/*
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_accountactions"] = &CmdSetAccountActions{}
}
// Commander implementation
type CmdSetAccountActions struct {
rpcMethod string
rpcParams *utils.TPAccountActions
rpcResult string
}
// 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) RpcMethod() string {
return self.rpcMethod
}
func (self *CmdSetAccountActions) RpcParams() interface{} {
return self.rpcParams
}
func (self *CmdSetAccountActions) RpcResult() interface{} {
return &self.rpcResult
}

View File

@@ -0,0 +1,69 @@
/*
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

@@ -1,3 +1,21 @@
/*
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 (

View File

@@ -1,34 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis #
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = mongo
logdb_host = localhost # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_name = cgrates # The name of the database to connect to.
[balancer]
enabled = true # Start balancer server
listen = 127.0.0.1:2001 # Balancer listen interface
rpc_encoding = gob # use JSON for RPC encoding
[rater]
enabled = true
listen = 127.0.0.1:1234 # listening address host:port, internal for internal communication only
balancer = disabled # if defined it will register to balancer as worker
rpc_encoding = gob # use JSON for RPC encoding

View File

@@ -1,32 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis #
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = mongo
logdb_host = localhost # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_name = cgrates # The name of the database to connect to.
[balancer]
enabled = true # Start balancer server
listen = 127.0.0.1:2001 # Balancer listen interface
rpc_encoding = gob # use JSON for RPC encoding
[rater]
enabled = false

View File

@@ -5,68 +5,131 @@
# [global] must exist in all files, rest of the configuration is inter-changeable.
[global]
# datadb_type = redis # The main database: <redis>.
# datadb_host = 127.0.0.1 # Database host address.
# datadb_port = 6379 # Port to reach the database.
# datadb_name = 10 # The name of the database to connect to.
# datadb_user = # Username to use when connecting to database.
# datadb_passwd = # Password to use when connecting to database.
# logdb_type = mongo # Log/stored database type to use: <same|postgres|mongo|redis>
# logdb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets.
# logdb_port = 27017 # The port to reach the logdb.
# logdb_name = cgrates # The name of the log database to connect to.
# logdb_user = # Username to use when connecting to logdb.
# logdb_passwd = # Password to use when connecting to logdb.
# ratingdb_type = redis # Rating subsystem database: <redis>.
# ratingdb_host = 127.0.0.1 # Rating subsystem database host address.
# ratingdb_port = 6379 # Rating subsystem port to reach the database.
# ratingdb_name = 10 # Rating subsystem database name to connect to.
# ratingdb_user = # Rating subsystem username to use when connecting to database.
# ratingdb_passwd = # Rating subsystem password to use when connecting to database.
# accountdb_type = redis # Accounting subsystem database: <redis>.
# accountdb_host = 127.0.0.1 # Accounting subsystem database host address.
# accountdb_port = 6379 # Accounting subsystem port to reach the database.
# accountdb_name = 11 # Accounting subsystem database name to connect to.
# accountdb_user = # Accounting subsystem username to use when connecting to database.
# accountdb_passwd = # Accounting subsystem password to use when connecting to database.
# stordb_type = mysql # Stor database type to use: <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.
# 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
# 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_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
[balancer]
# enabled = false # Start Balancer service: <true|false>.
# listen = 127.0.0.1:2012 # Balancer listen interface: <disabled|x.y.z.y:1234>.
# rpc_encoding = gob # RPC encoding used: <gob|json>.
[rater]
# enabled = false # Enable Rater service: <true|false>.
# balancer = disabled # Register to Balancer as worker: <enabled|disabled>.
# listen = 127.0.0.1:2012 # Rater's listening interface: <internal|x.y.z.y:1234>.
# rpc_encoding = gob # RPC encoding used: <gob|json>.
# enabled = false # Enable RaterCDRSExportPath service: <true|false>.
# balancer = # Register to Balancer as worker: <""|internal|127.0.0.1:2013>.
[scheduler]
# enabled = false # Starts Scheduler service: <true|false>.
[cdrs]
# 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>
[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>
[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>]
[mediator]
# enabled = false # Starts Mediator service: <true|false>.
# rater = 127.0.0.1:2012 # Address where to reach the Rater.
# 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.
# rpc_encoding = gob # RPC encoding used when talking to Rater: <gob|json>.
# skipdb = false # Skips database checks for previous recorded prices: <true|false>.
# pseudoprepaid = false # Execute debits together with pricing: <true|false>.
# cdr_type = freeswitch_cdr # CDR type <freeswitch_cdr>.
# cdr_in_dir = /var/log/freeswitch/cdr-csv # Absolute path towards the directory where the CDRs are kept.
# cdr_out_dir = /var/log/cgrates/cdr_out # Absolute path towards the directory where processed CDRs will be exported.
# 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.
[session_manager]
# enabled = false # Starts SessionManager service: <true|false>.
# switch_type = freeswitch # Defines the type of switch behind: <freeswitch>.
# rater = 127.0.0.1:2012 # Address where to reach the Rater.
# rater = internal # Address where to reach the Rater.
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
# debit_interval = 5 # Interval to perform debits on.
# rpc_encoding = gob # RPC encoding used when talking to Rater: <gob|json>.
# default_reqtype = # Default request type to consider when missing from requests: <""|prepaid|postpaid>.
# default_tor = 0 # Default Type of Record to consider when missing from requests.
# default_tenant = 0 # Default Tenant to consider when missing from requests.
# default_subject = 0 # Default rating Subject to consider when missing from requests.
# 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>.
[freeswitch]
# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
# passwd = ClueCon # FreeSWITCH socket password.
# reconnects = 5 # Number of attempts on connect failure.
# uuid_index = 10 # Index of the UUID info in the CDR file.
# direction_index = -1 # Index of the CallDirection info in the CDR file.
# tor_index = -1 # Index of the TypeOfRecord info in the CDR file.
# tenant_index = -1 # Index of the Tenant info in the CDR file.
# subject_index = -1 # Index of the Subject info in the CDR file. -1 to query database instead of rater
# account_index = -1 # Index of the Account info in the CDR file.
# destination_index = -1 # Index of the Destination info in the CDR file.
# time_start_index = -1 # Index of the TimeStart info in the CDR file.
# duration_index = -1 # Index of the CallDuration info in the CDR file.
[history_server]
# 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
[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
# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out

View File

@@ -1,66 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis # The main database: redis|mongo|postgres.
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = mongo # The logging database: redis|mongo|postgres|same.
logdb_host = localhost # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_name = cgrates # The name of the database to connect to.
[balancer]
enabled = false # Start balancer server
listen = 127.0.0.1:2001 # Balancer listen interface
rpc_encoding = gob # Use json or gob for RPC encoding
[rater]
enabled = true # Start the rating service
listen = 127.0.0.1:2001 # Listening address host:port, internal for internal communication only
balancer = disabled # If defined it will register to balancer as worker
rpc_encoding = gob # Use json or gob for RPC encoding
[mediator]
enabled = true # Start the mediator service
cdr_path = /var/log/freeswitch # Freeswitch Master CSV CDR path
cdr_out_path = /var/log/freeswitch/out # Freeswitch Master CSV CDR path
rater = internal # Address where to access rater. Can be internal, direct rater address or the address of a balancer
rpc_encoding = gob # Use json or gob for RPC encoding
skipdb = true # Do not look in the database for logged cdrs, ask rater directly
[scheduler]
enabled = true # Start the schedule service
[session_manager]
enabled = true # Start the session manager service
switch_type = freeswitch # The switch type to be used
debit_period = 10 # The number of seconds to be debited in advance during a call
rater = 127.0.0.1:2000 # Address where to access rater. Can be internal, direct rater address or the address of a balancer
rpc_encoding = gob # Use json or gob for RPC encoding
[freeswitch]
server = localhost:8021 # Freeswitch address host:port
pass = ClueCon # Freeswtch address host:port
direction_index = 0 # the position of elements in the Master.csv file
tor_index = 1
tenant_index = 2
subject_index = 3
account_index = 4
destination_index = 5
time_start_index = 6
time_end_index = 7

View File

@@ -1,42 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = mongo
logdb_host = localhost # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_name = cgrates # The name of the database to connect to.
[mediator]
enabled = true
cdr_path = /tmp/cgrates # Freeswitch Master CSV CDR file.
cdr_out_path = /tmp/cgrates/out # Freeswitch Master CSV CDR file.
rater = internal #address where to access rater. Can be internal, direct rater address or the address of a balancer
rpc_encoding = gob # use JSON for RPC encoding
skipdb = false
pseudo_prepaid = false
[freeswitch]
direction_index = 0
tor_index = 0
tenant_index = 1
subject_index = 2
account_index = 2
destination_index = 2
time_start_index = 4
duration_index = 5

View File

@@ -1,29 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = mongo
logdb_host = localhost # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_name = cgrates # The name of the database to connect to.
[rater]
enabled = true
listen = 127.0.0.1:1234 # listening address host:port, internal for internal communication only
balancer = 127.0.0.1:2001 # if defined it will register to balancer as worker
rpc_encoding = gob # use JSON for RPC encoding

View File

@@ -1,27 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = same
[rater]
enabled = true
listen = 127.0.0.1:2001 # listening address host:port, internal for internal communication only
balancer = disabled # if defined it will register to balancer as worker
rpc_encoding = gob # use JSON for RPC encoding

View File

@@ -0,0 +1,35 @@
# CGRateS Configuration file
#
# 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.
[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
[cdrc]
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>.

View File

@@ -0,0 +1,20 @@
<?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>
</configuration>
</document>

View File

@@ -0,0 +1,13 @@
# CGRateS Configuration file
#
# 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]
xmlcfg_path = /usr/share/cgrates/conf/samples/cgr_addconfig.xml # Path towards additional config defined in xml file
[cdre]
cdr_format = fixed_width # Exported CDRs format <csv>
export_template = *xml:CDREFW-A # Exported fields template <""|fld1,fld2|*xml:instance_name>

View File

@@ -0,0 +1,20 @@
# 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>

View File

@@ -1,24 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis #
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = same
[scheduler]
enabled = true

View File

@@ -1,37 +0,0 @@
# Rating system designed to be used in VoIP Carriers World
# Copyright (C) 2012 Radu Ioan Fericean
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
[global]
datadb_type = redis
datadb_host = 127.0.0.1:6379 # The host to connect to. Values that start with / are for UNIX domain sockets.
datadb_name = 10 # The name of the database to connect to.
logdb_type = mongo
logdb_host = localhost # The host to connect to. Values that start with / are for UNIX domain sockets.
logdb_name = cgrates # The name of the database to connect to.
[session_manager]
enabled = true
switch_type = freeswitch
debit_period = 10
rater = internal #address where to access rater. Can be internal, direct rater address or the address of a balancer
rpc_encoding = gob
[scheduler]
enabled = true
[freeswitch]
server = localhost:8021
pass = ClueCon

View File

@@ -1,4 +1,4 @@
#!/bin/sh
echo Running CGRateS service
exec /usr/bin/cgr-rater -config /etc/cgrates/cgrates.cfg
echo Firing up CGRateS engine
exec setuidgid cgrates /usr/bin/cgr-engine -config /etc/cgrates/cgrates.cfg

View File

@@ -0,0 +1,23 @@
<configuration name="cdr_csv.conf" description="CDR CSV Format">
<settings>
<!-- 'cdr-csv' will always be appended to log-base -->
<!--<param name="log-base" value="/var/log"/>-->
<param name="default-template" value="example"/>
<!-- This is like the info app but after the call is hung up -->
<!--<param name="debug" value="true"/>-->
<param name="rotate-on-hup" value="true"/>
<!-- may be a b or ab -->
<param name="legs" value="a"/>
<!-- Only log in Master.csv -->
<!-- <param name="master-file-only" value="true"/> -->
</settings>
<templates>
<template name="sql">INSERT INTO cdr VALUES ("${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}");</template>
<template name="example">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}","${read_codec}","${write_codec}"</template>
<template name="snom">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}","${read_codec}","${write_codec}","${sip_user_agent}","${call_clientcode}","${sip_rtp_rxstat}","${sip_rtp_txstat}","${sofia_record_file}"</template>
<template name="linksys">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}","${read_codec}","${write_codec}","${sip_user_agent}","${sip_p_rtp_stat}"</template>
<template name="asterisk">"${accountcode}","${caller_id_number}","${destination_number}","${context}","${caller_id}","${channel_name}","${bridge_channel}","${last_app}","${last_arg}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${amaflags}","${uuid}","${userfield}"</template>
<template name="opencdrrate">"${uuid}","${signal_bond}","${direction}","${ani}","${destination_number}","${answer_stamp}","${end_stamp}","${billsec}","${accountcode}","${userfield}","${network_addr}","${regex('${original_caller_id_name}'|^.)}","${sip_gateway_name}"</template>
</templates>
</configuration>

View File

@@ -0,0 +1,51 @@
<configuration name="json_cdr.conf" description="JSON CDR">
<settings>
<!-- Global parameters -->
<param name="log-b-leg" value="false"/>
<param name="prefix-a-leg" value="false"/>
<!-- Whether to URL encode the individual JSON values. Defaults to true, set to false for standard JSON. -->
<param name="encode-values" value="false"/>
<!-- Normally if url and log-dir are present, url is attempted first and log-dir second. This options allows to do both systematically. -->
<param name="log-http-and-disk" value="false"/>
<!-- File logging -->
<!-- Directory where to create the "json_cdr" directory used to store JSON CDRs. Leave empty for no file logging. -->
<!-- Might be overriden by a channel variable "json_cdr_base". -->
<param name="log-dir" value=""/>
<!-- Whether to rotate file CDRs. -->
<param name="rotate" value="false"/>
<!-- HTTP(S) logging -->
<!-- URL where to POST JSON CDRs. Leave empty for no URL logging. Up to 20 URLs may be specified. -->
<param name="url" value="http://127.0.0.1:2080/freeswitch_json"/>
<!-- Authentication scheme for the above URL. May be one of basic|digest|NTLM|GSS-NEGOTIATE|any-->
<param name="auth-scheme" value="basic"/>
<!-- Credentials in the form usernameassword if auth-scheme is used. Leave empty for no authentication. -->
<param name="cred" value="string"/>
<!-- Whether to base64 encode the entire JSON document before POSTing it. -->
<param name="encode" value="base64|true|false"/>
<!-- Number of retries in case of failure. Each specified URL is tried in turn. -->
<param name="retries" value="0"/>
<!-- Delay between retries (ms). -->
<param name="delay" value="5000"/>
<!-- Disable streaming if the server doesn't support it. -->
<param name="disable-100-continue" value="false"/>
<!-- If web posting failed, the CDR is written to a file. -->
<!-- Error log dir ("json_cdr" is appended). Up to 20 may be specified. Default to log-dir if none is specified. -->
<param name="err-log-dir" value=""/>
<!-- SSL options -->
<param name="ssl-key-path" value=""/>
<param name="ssl-key-password" value=""/>
<!-- SSL version. If specified, must be either "SSLv3" or "TLSv1". -->
<param name="ssl-version" value=""/>
<param name="enable-ssl-verifyhost" value="false"/>
<param name="ssl-cert-path" value=""/>
<param name="enable-cacert-check" value="false"/>
<param name="ssl-cacert-file" value=""/>
</settings>
</configuration>

9
data/monit/cgrates.monit Normal file
View File

@@ -0,0 +1,9 @@
# CGRateS Monit check script
check process CGRateS with pidfile /var/run/cgrates/cgr-engine.pid
start program = "/etc/init.d/cgrates start"
stop program = "/etc/init.d/cgrates stop"
if failed host 127.0.0.1 port 2012 type TCP 4 times within 4 cycles then restart # Rater
if failed host 127.0.0.1 port 2013 type TCP 4 times within 4 cycles then restart # History
if failed host 127.0.0.1 port 2080 type TCP 4 times within 4 cycles then restart # CDRS
if 5 restarts within 20 cycles then timeout

View File

@@ -1,6 +0,0 @@
## CGRateS official APT repository.
# Place this source file into your /etc/apt/sources.list.d/ folder and execute commands from bellow
# wget -O - http://apt.itsyscom.com/repos/apt/conf/cgrates.gpg.key|apt-key add -
# apt-get update && apt-get install
deb http://apt.itsyscom.com/repos/apt/debian squeeze main

View File

@@ -1,4 +0,0 @@
Tenant,Account,Direction,ActionTimingsTag,ActionTriggersTag
CUSTOMER_1,rif,OUT,STANDARD_ABO,STANDARD_TRIGGER
CUSTOMER_1,dan,OUT,STANDARD_ABO,STANDARD_TRIGGER
vdf,minitsboy,OUT,MORE_MINUTES,STANDARD_TRIGGER
1 Tenant Account Direction ActionTimingsTag ActionTriggersTag
2 CUSTOMER_1 rif OUT STANDARD_ABO STANDARD_TRIGGER
3 CUSTOMER_1 dan OUT STANDARD_ABO STANDARD_TRIGGER
4 vdf minitsboy OUT MORE_MINUTES STANDARD_TRIGGER

View File

@@ -1,7 +0,0 @@
Tag,ActionsTag,TimingTag, Weight
STANDARD_ABO,SOME,WEEKLY_SAME_TIME,10
STANDARD_ABO,SOME,WEEKLY_SAME_TIME,10
STANDARD_ABO,SOME,WEEKLY_SAME_TIME,10
STANDARD_ABO,SOME,ONE_TIME_RUN,10
STANDARD_ABO,SOME,FIRST_DAY_OF_MONTH,10
MORE_MINUTES,MINI,ONE_TIME_RUN,10
1 Tag ActionsTag TimingTag Weight
2 STANDARD_ABO SOME WEEKLY_SAME_TIME 10
3 STANDARD_ABO SOME WEEKLY_SAME_TIME 10
4 STANDARD_ABO SOME WEEKLY_SAME_TIME 10
5 STANDARD_ABO SOME ONE_TIME_RUN 10
6 STANDARD_ABO SOME FIRST_DAY_OF_MONTH 10
7 MORE_MINUTES MINI ONE_TIME_RUN 10

View File

@@ -1,5 +0,0 @@
Tag,BalanceTag,Direction,ThresholdValue,DestinationTag,ActionsTag,Weight
STANDARD_TRIGGER,MONETARY,OUT,30,*all,SOME_1,10
STANDARD_TRIGGER,SMS,OUT,30,*all,SOME_2,10
STANDARD_TRIGGER,MINUTES,OUT,10,GERMANY_O2,SOME_1,10
STANDARD_TRIGGER,MINUTES,OUT,200,GERMANY,SOME_2,10
1 Tag BalanceTag Direction ThresholdValue DestinationTag ActionsTag Weight
2 STANDARD_TRIGGER MONETARY OUT 30 *all SOME_1 10
3 STANDARD_TRIGGER SMS OUT 30 *all SOME_2 10
4 STANDARD_TRIGGER MINUTES OUT 10 GERMANY_O2 SOME_1 10
5 STANDARD_TRIGGER MINUTES OUT 200 GERMANY SOME_2 10

View File

@@ -1,9 +0,0 @@
Tag,Action,BalanceTag,Direction,Units,DestinationTag,PriceType,PriceValue,MinutesWeight,Weight
SOME,TOPUP_RESET,MONETARY,OUT,10,*all,,,,10
SOME,TOPUP_RESET,SMS,OUT,100,*all,,,,10
SOME,TOPUP_RESET,INTERNET,OUT,1000,*all,,,,10
SOME,RESET_POSTPAID,MONETARY,OUT,10,*all,,,,10
SOME,DEBIT,MONETARY,OUT,5,*all,,,,10
SOME_1,DEBIT,MINUTES,OUT,10,GERMANY_O2,PERCENT,25,10,10
SOME_2,TOPUP_RESET,MINUTES,OUT,1000,GERMANY,ABSOLUTE,0.2,10,10
MINI,TOPUP,MINUTES,OUT,100,NAT,ABSOLUTE,0,10,10
1 Tag Action BalanceTag Direction Units DestinationTag PriceType PriceValue MinutesWeight Weight
2 SOME TOPUP_RESET MONETARY OUT 10 *all 10
3 SOME TOPUP_RESET SMS OUT 100 *all 10
4 SOME TOPUP_RESET INTERNET OUT 1000 *all 10
5 SOME RESET_POSTPAID MONETARY OUT 10 *all 10
6 SOME DEBIT MONETARY OUT 5 *all 10
7 SOME_1 DEBIT MINUTES OUT 10 GERMANY_O2 PERCENT 25 10 10
8 SOME_2 TOPUP_RESET MINUTES OUT 1000 GERMANY ABSOLUTE 0.2 10 10
9 MINI TOPUP MINUTES OUT 100 NAT ABSOLUTE 0 10 10

View File

@@ -1,12 +0,0 @@
Tag,Prefix
GERMANY,49
GERMANY_O2,41
GERMANY_PREMIUM,43
ALL,49
ALL,41
ALL,43
NAT,0256
NAT,0257
NAT,0723
RET,0723
RET,0724
1 Tag Prefix
2 GERMANY 49
3 GERMANY_O2 41
4 GERMANY_PREMIUM 43
5 ALL 49
6 ALL 41
7 ALL 43
8 NAT 0256
9 NAT 0257
10 NAT 0723
11 RET 0723
12 RET 0724

View File

@@ -1,9 +0,0 @@
Tag,RatesTag,TimingProfile,Weight
STANDARD,RT_STANDARD,WORKDAYS_00,10
STANDARD,RT_STD_WEEKEND,WORKDAYS_18,10
STANDARD,RT_STD_WEEKEND,WEEKENDS,10
PREMIUM,RT_STD_WEEKEND,WEEKENDS,10
DEFAULT,RT_DEFAULT,WORKDAYS_00,10
EVENING,P1,WORKDAYS_00,10
EVENING,P2,WORKDAYS_18,10
EVENING,P2,WEEKENDS,10
1 Tag RatesTag TimingProfile Weight
2 STANDARD RT_STANDARD WORKDAYS_00 10
3 STANDARD RT_STD_WEEKEND WORKDAYS_18 10
4 STANDARD RT_STD_WEEKEND WEEKENDS 10
5 PREMIUM RT_STD_WEEKEND WEEKENDS 10
6 DEFAULT RT_DEFAULT WORKDAYS_00 10
7 EVENING P1 WORKDAYS_00 10
8 EVENING P2 WORKDAYS_18 10
9 EVENING P2 WEEKENDS 10

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