mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Compare commits
969 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24fda5b14b | ||
|
|
f3f6bb1e16 | ||
|
|
9211c01d69 | ||
|
|
87481ed520 | ||
|
|
5857e63823 | ||
|
|
9ebf2573b0 | ||
|
|
e67db4a434 | ||
|
|
375bf8c0dd | ||
|
|
6acfa22a04 | ||
|
|
29e3bd137b | ||
|
|
f390281f41 | ||
|
|
881db9c1c4 | ||
|
|
def252d153 | ||
|
|
2bc4e3fb7e | ||
|
|
e381265ace | ||
|
|
15f2a2cd82 | ||
|
|
4bb3e745f1 | ||
|
|
4045d022ae | ||
|
|
5e06cafd2f | ||
|
|
a93ecfc049 | ||
|
|
5d0e6b4fee | ||
|
|
5de1a83bf5 | ||
|
|
c6de153685 | ||
|
|
22adfb552f | ||
|
|
b0cd66509c | ||
|
|
4d02ede7df | ||
|
|
d77c5463ac | ||
|
|
19b7d0beb7 | ||
|
|
315eddb63f | ||
|
|
acda24e46e | ||
|
|
af16503069 | ||
|
|
822a921a37 | ||
|
|
6017827059 | ||
|
|
3892d9ed20 | ||
|
|
5322063103 | ||
|
|
e8393b9bcc | ||
|
|
724ebdc039 | ||
|
|
2dbc80166e | ||
|
|
ccac8236f4 | ||
|
|
3c34310ab2 | ||
|
|
2ef0f4f492 | ||
|
|
f6741c6d88 | ||
|
|
62e9d02239 | ||
|
|
2e80f8a1f4 | ||
|
|
3aecec9bb8 | ||
|
|
e2e3b8d2f2 | ||
|
|
86317e4b19 | ||
|
|
f2f46f8054 | ||
|
|
c66f4f2710 | ||
|
|
ee31976401 | ||
|
|
f6d16cecc5 | ||
|
|
db433a760f | ||
|
|
47700a924f | ||
|
|
548a4ea64f | ||
|
|
3227434fd9 | ||
|
|
c344554bbb | ||
|
|
57be22dd87 | ||
|
|
a3cc1fedfd | ||
|
|
6685b7b444 | ||
|
|
10b27fa978 | ||
|
|
e2a50a77fd | ||
|
|
00b47c1367 | ||
|
|
cdd83ea531 | ||
|
|
d33299f0ab | ||
|
|
5083c7479d | ||
|
|
37b21d2bb5 | ||
|
|
e09cc8527e | ||
|
|
0855d09fea | ||
|
|
c8553e9611 | ||
|
|
d8fb33aee3 | ||
|
|
3f30cdb7ed | ||
|
|
b9681f2d1c | ||
|
|
d6a0825142 | ||
|
|
8d61099de1 | ||
|
|
6ba20aee03 | ||
|
|
b28a02512c | ||
|
|
5ae7a18283 | ||
|
|
5b10f63c94 | ||
|
|
90e3791c0d | ||
|
|
fdeecafe37 | ||
|
|
71a50dc78e | ||
|
|
8f47264248 | ||
|
|
a753a55119 | ||
|
|
2556fb5e69 | ||
|
|
0b0474fa23 | ||
|
|
8967c2fa3a | ||
|
|
bea8434d99 | ||
|
|
bd5b2607e8 | ||
|
|
62ccb5b9df | ||
|
|
755ab02d55 | ||
|
|
cf0179e505 | ||
|
|
bde488c46b | ||
|
|
68aefe4912 | ||
|
|
487a1c5da6 | ||
|
|
64711c6b87 | ||
|
|
781db890d1 | ||
|
|
7b618f8806 | ||
|
|
9006c5ed18 | ||
|
|
e6b02b84c0 | ||
|
|
b557e2a5a4 | ||
|
|
9a06d5e537 | ||
|
|
be8ea3bcc9 | ||
|
|
4833eb94c3 | ||
|
|
f717bb9542 | ||
|
|
2646e8fa6e | ||
|
|
ed313c3222 | ||
|
|
25b15b8f0f | ||
|
|
7d9326965d | ||
|
|
52ae44fc34 | ||
|
|
a0aa0cc05d | ||
|
|
b9d52c6401 | ||
|
|
48d1720430 | ||
|
|
39e93e7fb8 | ||
|
|
49a3df285b | ||
|
|
fc91e35433 | ||
|
|
95d4af8ab7 | ||
|
|
83dd4efeab | ||
|
|
b3dee97bc4 | ||
|
|
074313b0f8 | ||
|
|
8d98436656 | ||
|
|
477af9467f | ||
|
|
1a8e7f4631 | ||
|
|
670b748b01 | ||
|
|
ddbd97ad20 | ||
|
|
fe4506008f | ||
|
|
0aa5223f78 | ||
|
|
a37dbf0734 | ||
|
|
7831a53797 | ||
|
|
f701a42948 | ||
|
|
164b9f8945 | ||
|
|
a3daecdbc9 | ||
|
|
a91873800d | ||
|
|
f463dd2755 | ||
|
|
f052b14214 | ||
|
|
5f1923d694 | ||
|
|
c52b161b73 | ||
|
|
d7a0446585 | ||
|
|
216ae2b767 | ||
|
|
e60fc8cb96 | ||
|
|
8008c76154 | ||
|
|
d929e14159 | ||
|
|
dd2eb2f97a | ||
|
|
e0476a3130 | ||
|
|
fdc451244a | ||
|
|
098a1f6d3f | ||
|
|
75046b1f36 | ||
|
|
dec4ebf2f1 | ||
|
|
4154a3beef | ||
|
|
0efb2f1094 | ||
|
|
5371688158 | ||
|
|
51ee4091bc | ||
|
|
540412033f | ||
|
|
a731a33238 | ||
|
|
96092faffc | ||
|
|
beea40977c | ||
|
|
ffaeac112c | ||
|
|
9c46b61c73 | ||
|
|
1ab852e3cd | ||
|
|
4e956eaa8c | ||
|
|
a5f5274095 | ||
|
|
4c894ee9de | ||
|
|
361d75d8cf | ||
|
|
64e79764de | ||
|
|
6ad5794bfc | ||
|
|
c447fa49dc | ||
|
|
9042c98492 | ||
|
|
bd71dc9a4c | ||
|
|
944262ccff | ||
|
|
fc1e7aed5a | ||
|
|
b91fc4ca21 | ||
|
|
b784edf5aa | ||
|
|
914fe77117 | ||
|
|
77ea2754cb | ||
|
|
cd531849d1 | ||
|
|
dadf06f3ef | ||
|
|
9e88719e05 | ||
|
|
3a3b92e9d6 | ||
|
|
a9d698e029 | ||
|
|
aa99a1e526 | ||
|
|
f154dac933 | ||
|
|
59f650e3f8 | ||
|
|
462152a76d | ||
|
|
3bf3f04cc6 | ||
|
|
9f9174d0cc | ||
|
|
e6ef9f8155 | ||
|
|
bea20cf98d | ||
|
|
59e6e945b5 | ||
|
|
77c326cccf | ||
|
|
709594ffa9 | ||
|
|
58d485f46f | ||
|
|
4cbadb4158 | ||
|
|
caba3af732 | ||
|
|
727337e617 | ||
|
|
20a0ae4449 | ||
|
|
5f331f3161 | ||
|
|
c7ea2cd263 | ||
|
|
30a842a6b9 | ||
|
|
3f7bc14b44 | ||
|
|
6fcd794005 | ||
|
|
750260505c | ||
|
|
5c2d1a9c1a | ||
|
|
d90e67b966 | ||
|
|
0b7f315d49 | ||
|
|
a239b8d760 | ||
|
|
0490eaef8c | ||
|
|
ea6c8371d2 | ||
|
|
4032274748 | ||
|
|
edf5007f9e | ||
|
|
31eebb7e81 | ||
|
|
5d190f0f2a | ||
|
|
a5fad89574 | ||
|
|
23a676da95 | ||
|
|
69cf1cc896 | ||
|
|
b735688fe5 | ||
|
|
6a251c2b2c | ||
|
|
21b4c10836 | ||
|
|
1bbe5a0a88 | ||
|
|
8733b5219e | ||
|
|
f61aca8d91 | ||
|
|
3bbb67c72f | ||
|
|
8177e64f8b | ||
|
|
9210b20924 | ||
|
|
5b424d0e70 | ||
|
|
b6dde967f2 | ||
|
|
c50c202fe6 | ||
|
|
5f9d18fe0f | ||
|
|
a31231de09 | ||
|
|
ddeb13bcc4 | ||
|
|
70d0e0171c | ||
|
|
ba3cabb7bb | ||
|
|
5d857f1255 | ||
|
|
4f3e91d8ca | ||
|
|
7c52e1e692 | ||
|
|
81cc68e0c5 | ||
|
|
f51e0b10e3 | ||
|
|
999ac0aead | ||
|
|
02297d4d36 | ||
|
|
95fde288ad | ||
|
|
ea75cd3aa2 | ||
|
|
21875f7ed7 | ||
|
|
7697b74713 | ||
|
|
46b87953da | ||
|
|
5e72e1c528 | ||
|
|
baf590dab3 | ||
|
|
2c3e8e1584 | ||
|
|
962cf17884 | ||
|
|
ac60a39852 | ||
|
|
0005ba2f68 | ||
|
|
08e5d8540a | ||
|
|
6c96937059 | ||
|
|
080395e7ea | ||
|
|
2896053199 | ||
|
|
0348be416c | ||
|
|
13707ca377 | ||
|
|
8ec403f151 | ||
|
|
abd0a00ecb | ||
|
|
e97b72a30a | ||
|
|
a3048a696f | ||
|
|
4065bcb58f | ||
|
|
3c622d317a | ||
|
|
9f84950287 | ||
|
|
6c82f01a10 | ||
|
|
d8f2fc8521 | ||
|
|
9caed50e0a | ||
|
|
ea6337d9fa | ||
|
|
2a1f6ff583 | ||
|
|
657100c1e8 | ||
|
|
b7cdf53858 | ||
|
|
4738f7d882 | ||
|
|
2c1b96c6b3 | ||
|
|
99e269b894 | ||
|
|
187d9476f6 | ||
|
|
4b0ccb7564 | ||
|
|
a0d76f8dda | ||
|
|
765ab1c600 | ||
|
|
e6ffd56479 | ||
|
|
d28815bf0a | ||
|
|
77fa996a7d | ||
|
|
c0b6562a54 | ||
|
|
845ecc8fd9 | ||
|
|
49ab537be8 | ||
|
|
c50a61fec5 | ||
|
|
d765e0090b | ||
|
|
d776219013 | ||
|
|
9feadce362 | ||
|
|
9428ffa9c6 | ||
|
|
7bc182e374 | ||
|
|
993c5400aa | ||
|
|
758f399acb | ||
|
|
91bae964ba | ||
|
|
43c097aa98 | ||
|
|
30681fa7d4 | ||
|
|
c6bbd32298 | ||
|
|
157ef5359b | ||
|
|
8a323544e8 | ||
|
|
f6758d0d0f | ||
|
|
fb1c7265f5 | ||
|
|
995cfd9785 | ||
|
|
d56bec9338 | ||
|
|
d2ffa364a2 | ||
|
|
47236109b2 | ||
|
|
7f9681513b | ||
|
|
34f3dc9e0f | ||
|
|
0924f4104c | ||
|
|
e4b5c98367 | ||
|
|
ae5118dc0b | ||
|
|
77c7f205b0 | ||
|
|
ca6957cb48 | ||
|
|
8aafd0ec77 | ||
|
|
9f125e638f | ||
|
|
f393b600d4 | ||
|
|
2cd548b810 | ||
|
|
29edca794b | ||
|
|
127e7650fa | ||
|
|
66190b615a | ||
|
|
0278f08de4 | ||
|
|
84d16bcce5 | ||
|
|
7ee915e916 | ||
|
|
3208a78965 | ||
|
|
98fc855ee0 | ||
|
|
1471c47bda | ||
|
|
c8fb090c11 | ||
|
|
23a95ed890 | ||
|
|
1c2d2116fd | ||
|
|
12e896576c | ||
|
|
79497e8970 | ||
|
|
07dd19a9d5 | ||
|
|
3f16c1c12d | ||
|
|
3f506b50c8 | ||
|
|
b7d9da842b | ||
|
|
9854ce7068 | ||
|
|
58ddae9b70 | ||
|
|
afe845a8aa | ||
|
|
99a9b2c987 | ||
|
|
caa2ed7776 | ||
|
|
1a53359c5f | ||
|
|
b8075c2835 | ||
|
|
2a23a29b25 | ||
|
|
4481b170eb | ||
|
|
b122a2a748 | ||
|
|
4e4d411a6a | ||
|
|
6b2e0786d2 | ||
|
|
fdeab3cb09 | ||
|
|
7099b40609 | ||
|
|
3bc4823e1d | ||
|
|
fa2ded95bc | ||
|
|
eb3bed151d | ||
|
|
72a415b924 | ||
|
|
b8c83e7752 | ||
|
|
9f45635de0 | ||
|
|
4092baea56 | ||
|
|
5024944a2b | ||
|
|
ec5417d6e4 | ||
|
|
2b19fcd408 | ||
|
|
8064491481 | ||
|
|
9d5ed81791 | ||
|
|
4eee6ddd0e | ||
|
|
7f8f981085 | ||
|
|
b3a3c1f599 | ||
|
|
756affe31a | ||
|
|
1db3c550b1 | ||
|
|
e76b94c285 | ||
|
|
75d24d0fd1 | ||
|
|
e19b8bc4cf | ||
|
|
c04bc753fd | ||
|
|
d60a5f1b79 | ||
|
|
1950551833 | ||
|
|
8e95225b99 | ||
|
|
c38179c01a | ||
|
|
ca3b13651f | ||
|
|
08be8d1bb5 | ||
|
|
d8977fc504 | ||
|
|
2ba458524e | ||
|
|
a74a201735 | ||
|
|
6fb2ca37c2 | ||
|
|
74f027189a | ||
|
|
990000f246 | ||
|
|
4330e9dd59 | ||
|
|
3598b7b505 | ||
|
|
17af9c312a | ||
|
|
f3d0c49803 | ||
|
|
f7d7a34c17 | ||
|
|
6d543f5d86 | ||
|
|
18273a920e | ||
|
|
f136f1e67d | ||
|
|
4638bad4d1 | ||
|
|
e03bbc9b96 | ||
|
|
522ef1cf9c | ||
|
|
cd9eaecb83 | ||
|
|
806570cbaf | ||
|
|
c0d7cf0001 | ||
|
|
d69378a626 | ||
|
|
3e4067b4b0 | ||
|
|
dbe8bde7bf | ||
|
|
d0284bf5e1 | ||
|
|
41dd86ca9a | ||
|
|
609f6271df | ||
|
|
aa2360f756 | ||
|
|
243ebdb7fa | ||
|
|
022c426a20 | ||
|
|
5a929dfeb0 | ||
|
|
237508e590 | ||
|
|
59543849d4 | ||
|
|
8a36ac72b9 | ||
|
|
fac3fa5025 | ||
|
|
950a357127 | ||
|
|
796caa1c7f | ||
|
|
a57686990c | ||
|
|
d99c0c076c | ||
|
|
b6e5ea82e0 | ||
|
|
895bf6b6c1 | ||
|
|
da2ac4ea28 | ||
|
|
c68660367b | ||
|
|
76af5ddf17 | ||
|
|
690751e3c2 | ||
|
|
00be23afb1 | ||
|
|
d5f4417005 | ||
|
|
6c30c98836 | ||
|
|
6ff0d17d5f | ||
|
|
3611494936 | ||
|
|
1c4c27b2bf | ||
|
|
7809fa4e8e | ||
|
|
d95dc46e65 | ||
|
|
395931aba8 | ||
|
|
b122090421 | ||
|
|
6f9a3ab4c0 | ||
|
|
71ca9d8fe3 | ||
|
|
3257010f32 | ||
|
|
f0fbe79fca | ||
|
|
e90960e7fc | ||
|
|
b5d938a767 | ||
|
|
a3d4b9dd8b | ||
|
|
10dea0c55d | ||
|
|
131997507d | ||
|
|
086d1e7ab2 | ||
|
|
389a81ae4c | ||
|
|
300153f961 | ||
|
|
e6ae067db1 | ||
|
|
51379946d9 | ||
|
|
95b6cbbdc8 | ||
|
|
3fb00f8fea | ||
|
|
4de7dd3c26 | ||
|
|
1388e7caab | ||
|
|
66c6efab96 | ||
|
|
92f9849380 | ||
|
|
5b34dcebd4 | ||
|
|
14b9c5d6c9 | ||
|
|
8482dde7e8 | ||
|
|
c0a48ad33a | ||
|
|
f1e4b57b2c | ||
|
|
2ffaf09ea1 | ||
|
|
51c3a8b2ff | ||
|
|
eebfa68375 | ||
|
|
148ef2e5a1 | ||
|
|
cc27c6a1a1 | ||
|
|
92594be302 | ||
|
|
bb9f8bfd7b | ||
|
|
490e8043d6 | ||
|
|
69200d00e8 | ||
|
|
453f4c9f33 | ||
|
|
9527757790 | ||
|
|
fc4696cdaf | ||
|
|
0c6e524c78 | ||
|
|
3b8d9c9082 | ||
|
|
52d91e2fb4 | ||
|
|
0b5ca3fe36 | ||
|
|
b669d1d326 | ||
|
|
6138a12974 | ||
|
|
8e92c51da2 | ||
|
|
8bae6e2de7 | ||
|
|
859890f0b4 | ||
|
|
f203feab20 | ||
|
|
ed863123f6 | ||
|
|
b45200ccd3 | ||
|
|
d4ea254bb5 | ||
|
|
fef08d8a27 | ||
|
|
9c693e21d0 | ||
|
|
865c3656fd | ||
|
|
bd4a19ae16 | ||
|
|
c380fcb118 | ||
|
|
fd12134596 | ||
|
|
83eb650c97 | ||
|
|
472d036d15 | ||
|
|
d3ddffd55e | ||
|
|
ade79936b5 | ||
|
|
5ff600fe18 | ||
|
|
fcb98adcf7 | ||
|
|
6e65376bec | ||
|
|
4fb7c404c1 | ||
|
|
6215289a37 | ||
|
|
3d523128e2 | ||
|
|
11932fd58c | ||
|
|
5ac4949f88 | ||
|
|
4a6ea0ed35 | ||
|
|
f69af88bd6 | ||
|
|
5d0a4cf470 | ||
|
|
fa6f91f608 | ||
|
|
a585f39b04 | ||
|
|
66815639af | ||
|
|
8d991672ab | ||
|
|
52b60db6b2 | ||
|
|
c7c4aefe0d | ||
|
|
87ea266c4a | ||
|
|
a20d6258e2 | ||
|
|
30d91c9747 | ||
|
|
87b6f9c12e | ||
|
|
a1bbe1c7a6 | ||
|
|
47415dadfc | ||
|
|
509f4a87cd | ||
|
|
58e26a46ce | ||
|
|
34b577c74d | ||
|
|
38f6b69193 | ||
|
|
d5c81818ee | ||
|
|
fb9483693f | ||
|
|
b4f34a77cc | ||
|
|
4b6b7758f8 | ||
|
|
a483d2304e | ||
|
|
352540ab04 | ||
|
|
4e0ccbfa19 | ||
|
|
f38f04f6c5 | ||
|
|
f5257b46a5 | ||
|
|
b50f950474 | ||
|
|
198b604407 | ||
|
|
210c52f041 | ||
|
|
6b5923f86f | ||
|
|
3a26ba86ea | ||
|
|
9df187dd8e | ||
|
|
9cf1aed058 | ||
|
|
f904973d25 | ||
|
|
adf015af5c | ||
|
|
b95d21fa4e | ||
|
|
d05cd92a8d | ||
|
|
719ed3ef51 | ||
|
|
751ec1e4c3 | ||
|
|
c2af82e8b0 | ||
|
|
8b29a35b96 | ||
|
|
65d828a2c7 | ||
|
|
26bb99bd64 | ||
|
|
408047dd1c | ||
|
|
c075d8182a | ||
|
|
d6238ba5f7 | ||
|
|
8189bbbf36 | ||
|
|
ef9f5fe612 | ||
|
|
4c19a2078b | ||
|
|
86bcbac221 | ||
|
|
20ad7a8487 | ||
|
|
c6752be776 | ||
|
|
5e7f7f6663 | ||
|
|
13ce865b6f | ||
|
|
64a0cda8c1 | ||
|
|
0b1d9c2da4 | ||
|
|
41d0bb80ac | ||
|
|
7bf11a216d | ||
|
|
bb340210f1 | ||
|
|
ce1c22f142 | ||
|
|
3614ed2624 | ||
|
|
d7854524c9 | ||
|
|
6f00f9b4c2 | ||
|
|
3790591eb2 | ||
|
|
99fc749c1c | ||
|
|
194c52db2d | ||
|
|
84fbf30376 | ||
|
|
25db02a6fb | ||
|
|
ec6de7e901 | ||
|
|
4a5d228d81 | ||
|
|
092853d571 | ||
|
|
0229adde44 | ||
|
|
ab68dcab79 | ||
|
|
09b3de0474 | ||
|
|
458a8f8628 | ||
|
|
3eda5495ea | ||
|
|
bb7b144f84 | ||
|
|
b87399c123 | ||
|
|
455ed9e547 | ||
|
|
68a010a476 | ||
|
|
131bc22609 | ||
|
|
01005b06f3 | ||
|
|
3ae3cadfec | ||
|
|
70571e15d0 | ||
|
|
fb80c6c2a8 | ||
|
|
4f7501a24c | ||
|
|
61d777b1eb | ||
|
|
848281697e | ||
|
|
d9b0461746 | ||
|
|
566447a0a0 | ||
|
|
f965e2ef72 | ||
|
|
386e927487 | ||
|
|
151d42cc7f | ||
|
|
dbed53fbbf | ||
|
|
a4559a68ea | ||
|
|
b343d89293 | ||
|
|
60c8469477 | ||
|
|
d8aeb0ea47 | ||
|
|
0e0bfe7af0 | ||
|
|
9e7b195ec6 | ||
|
|
3466fa103b | ||
|
|
d03ff0a5cd | ||
|
|
0573e98d21 | ||
|
|
dd03d344f6 | ||
|
|
e1ce1f5358 | ||
|
|
260c27861c | ||
|
|
c756c799f5 | ||
|
|
824302a8f5 | ||
|
|
d608c6fe07 | ||
|
|
6f8e318bfc | ||
|
|
f22f5a1bd9 | ||
|
|
a22260b317 | ||
|
|
5b4e7a1234 | ||
|
|
54ab6074a6 | ||
|
|
afe0dbc5cb | ||
|
|
c9c4a484eb | ||
|
|
9c6de26fa5 | ||
|
|
47c06835ea | ||
|
|
c0accc3d5e | ||
|
|
56b4a3f0a4 | ||
|
|
48ccb6e930 | ||
|
|
f286e0a189 | ||
|
|
f2a9154b41 | ||
|
|
b7a87d1e59 | ||
|
|
c59dc78f01 | ||
|
|
88f789cea9 | ||
|
|
4428c09748 | ||
|
|
97540fcf8c | ||
|
|
617c3f9591 | ||
|
|
036e363ca8 | ||
|
|
f1d2bc6164 | ||
|
|
df1cd36f50 | ||
|
|
dbdad3e3ce | ||
|
|
483a13f983 | ||
|
|
76d7e02402 | ||
|
|
f5059e55fa | ||
|
|
33f24534b3 | ||
|
|
85027f0764 | ||
|
|
7632a8f14e | ||
|
|
c889eafb20 | ||
|
|
8bd4ac1eba | ||
|
|
7db0919be0 | ||
|
|
fc00528007 | ||
|
|
5a6df95133 | ||
|
|
6cd7edd8a2 | ||
|
|
09238f7355 | ||
|
|
552aa0904f | ||
|
|
eec68807ba | ||
|
|
94addca3e5 | ||
|
|
6c35f6e340 | ||
|
|
17b982bdeb | ||
|
|
cd98141fbb | ||
|
|
3b98a75bab | ||
|
|
8c1ad3de86 | ||
|
|
f90b9e7aa3 | ||
|
|
9c5715bb7b | ||
|
|
1ff176cf47 | ||
|
|
8cda25cadb | ||
|
|
fa788103cf | ||
|
|
bb22d37fc6 | ||
|
|
e18420ea23 | ||
|
|
a35f5ed480 | ||
|
|
a1612f5507 | ||
|
|
1ee31ca004 | ||
|
|
372fbe0e10 | ||
|
|
2f6b1f3f84 | ||
|
|
f3f9d03c80 | ||
|
|
931c6efb1a | ||
|
|
f82a2c58a8 | ||
|
|
48d1953f9a | ||
|
|
5516c8ff1f | ||
|
|
3731fb1977 | ||
|
|
8f804b8bc5 | ||
|
|
5083b54bcd | ||
|
|
637e146ba2 | ||
|
|
7edcb9fd6e | ||
|
|
9004ef1075 | ||
|
|
20f1bf27c3 | ||
|
|
17fe5b3351 | ||
|
|
47442dda3c | ||
|
|
ed6c1fc151 | ||
|
|
5acbdc6934 | ||
|
|
411af6ba74 | ||
|
|
1bfaa3a52f | ||
|
|
8a6d7a897b | ||
|
|
9cce7c6449 | ||
|
|
860f00080d | ||
|
|
3c6b5051af | ||
|
|
ba3d3c450d | ||
|
|
c651aabb32 | ||
|
|
08c4386e65 | ||
|
|
c3102e8375 | ||
|
|
bf772ada3f | ||
|
|
609c821a50 | ||
|
|
af64c50fe6 | ||
|
|
2448507578 | ||
|
|
9409a4b65e | ||
|
|
09bbf2af8f | ||
|
|
c2913b6fe9 | ||
|
|
99992ab38a | ||
|
|
7b559fa68d | ||
|
|
154a9ab00b | ||
|
|
85a7e434b7 | ||
|
|
30d731d4c5 | ||
|
|
1c912c8974 | ||
|
|
c14489222b | ||
|
|
b3e9a84f6f | ||
|
|
86919f831f | ||
|
|
54704b8cdd | ||
|
|
38df076010 | ||
|
|
edd2c704de | ||
|
|
b894ab8236 | ||
|
|
b799dcc8ae | ||
|
|
c639c13647 | ||
|
|
e27bcaabf3 | ||
|
|
a67e410ed8 | ||
|
|
a9f03893b5 | ||
|
|
d31536c249 | ||
|
|
48fb53ac08 | ||
|
|
305dca0fc9 | ||
|
|
cb20ae167f | ||
|
|
7a38b39631 | ||
|
|
c209dbfb9f | ||
|
|
0785874357 | ||
|
|
f07a0cc885 | ||
|
|
84dbe167f5 | ||
|
|
e027a2a6a1 | ||
|
|
5dcbb28dd6 | ||
|
|
9a0dc72d0d | ||
|
|
52afbd376e | ||
|
|
8a58dfd3dd | ||
|
|
be1c2fbb7a | ||
|
|
f002c2a224 | ||
|
|
609bbdcb2a | ||
|
|
8000887051 | ||
|
|
a814b1cc17 | ||
|
|
2d08f0a31e | ||
|
|
b04f276fb6 | ||
|
|
5f730090fc | ||
|
|
ff6b55c111 | ||
|
|
2156f05131 | ||
|
|
882a567fe1 | ||
|
|
58c16a5f47 | ||
|
|
8b6639f186 | ||
|
|
88db48470d | ||
|
|
864b7eca5a | ||
|
|
05c6ee4756 | ||
|
|
d1fd3e4e09 | ||
|
|
9411759bc9 | ||
|
|
80e5fa7403 | ||
|
|
06ed29ddd4 | ||
|
|
13e8f53230 | ||
|
|
8c8f003239 | ||
|
|
10f46f348a | ||
|
|
230571c632 | ||
|
|
8fac5a1ff1 | ||
|
|
13347532d6 | ||
|
|
b6b5c11033 | ||
|
|
4a9c4eff43 | ||
|
|
d0e185c47a | ||
|
|
457151ecce | ||
|
|
ed5ebc2efc | ||
|
|
1771d9de3a | ||
|
|
b57d321e30 | ||
|
|
5941823138 | ||
|
|
6c5d73d41a | ||
|
|
63a1fc6f31 | ||
|
|
8a457618be | ||
|
|
5bb67111b2 | ||
|
|
1b759af9e2 | ||
|
|
e5652558bc | ||
|
|
606d053844 | ||
|
|
59e474bf68 | ||
|
|
1d3ce69c5d | ||
|
|
11e7eed149 | ||
|
|
78d2ee4e28 | ||
|
|
67d9c6f64d | ||
|
|
d112b53dda | ||
|
|
2f733525b2 | ||
|
|
c60b26c0c4 | ||
|
|
d483a6669f | ||
|
|
d4017890ec | ||
|
|
ddc2240c67 | ||
|
|
9887799492 | ||
|
|
f1d94aebaf | ||
|
|
1a1403ace1 | ||
|
|
63d9612932 | ||
|
|
37c893d4f8 | ||
|
|
ee15ce5056 | ||
|
|
0be3f8b342 | ||
|
|
26d270c7c1 | ||
|
|
e5f0af9fb6 | ||
|
|
9f28fa0d24 | ||
|
|
ff9f8c82af | ||
|
|
64c1cd67f0 | ||
|
|
6afd7ee19f | ||
|
|
946a496a46 | ||
|
|
af811d669e | ||
|
|
352b58eccc | ||
|
|
c1139bc2a8 | ||
|
|
6c79fd2092 | ||
|
|
3d9fc28d82 | ||
|
|
14adc49f2e | ||
|
|
3ddc63f4f5 | ||
|
|
42cb2d9b4f | ||
|
|
10b40e2652 | ||
|
|
c14be129ed | ||
|
|
6d3e011634 | ||
|
|
2c7eb3593e | ||
|
|
8845b4b310 | ||
|
|
84acdd737b | ||
|
|
2805190f45 | ||
|
|
bcc0370c31 | ||
|
|
eb906ed87a | ||
|
|
081ddce3f3 | ||
|
|
06fef1536c | ||
|
|
77abee97db | ||
|
|
e4530fdd0e | ||
|
|
22f9297248 | ||
|
|
30de8c1c95 | ||
|
|
b8a9d77575 | ||
|
|
383bba3412 | ||
|
|
210f290e67 | ||
|
|
6c8d2c7187 | ||
|
|
17597375dc | ||
|
|
092a80d9d4 | ||
|
|
e7de9222d1 | ||
|
|
39923c3d5e | ||
|
|
6969606096 | ||
|
|
d5c4c717f3 | ||
|
|
72fd2984ad | ||
|
|
d75d1475fc | ||
|
|
2eb8c093ad | ||
|
|
0a3adf326e | ||
|
|
c0881d977d | ||
|
|
09f5db1a31 | ||
|
|
e29f295cb0 | ||
|
|
5ba6c6ccc9 | ||
|
|
ebbadb50dd | ||
|
|
b6bfc1745c | ||
|
|
8f3b146f71 | ||
|
|
6d595eee2d | ||
|
|
6f0ba9449b | ||
|
|
6aafc8cf5f | ||
|
|
45fd41a1a2 | ||
|
|
460b2470f1 | ||
|
|
ae3a6a786e | ||
|
|
c39b2d5a84 | ||
|
|
2be2bc97a0 | ||
|
|
526b50ed12 | ||
|
|
1ada3d929a | ||
|
|
3123b44501 | ||
|
|
34bc6107ea | ||
|
|
b7c6314a09 | ||
|
|
7b1b278c87 | ||
|
|
aa1c2021d3 | ||
|
|
341c96552d | ||
|
|
89af3fa82e | ||
|
|
f3dbbf77fc | ||
|
|
33febd9285 | ||
|
|
edbfd1b436 | ||
|
|
57c47987e2 | ||
|
|
f443d384d0 | ||
|
|
d3e16d3712 | ||
|
|
60bd0c0a08 | ||
|
|
b5092ebabe | ||
|
|
2b025a3fc9 | ||
|
|
2aaac1b9e1 | ||
|
|
b5a5b4631f | ||
|
|
49ecf65fdc | ||
|
|
8ecf706086 | ||
|
|
7d06363c10 | ||
|
|
dfa91442d4 | ||
|
|
d2baf4fa08 | ||
|
|
56b5ce9a49 | ||
|
|
0401809d0d | ||
|
|
f0d96157e4 | ||
|
|
6152ce6925 | ||
|
|
5d62eea4f2 | ||
|
|
2ed059ff7d | ||
|
|
389c173471 | ||
|
|
25df692872 | ||
|
|
db62f2cc85 | ||
|
|
7e308dca52 | ||
|
|
00e48bc23a | ||
|
|
8a7d8c7fd1 | ||
|
|
f89eecaef8 | ||
|
|
14ea78cc34 | ||
|
|
aff4813757 | ||
|
|
15e5709e29 | ||
|
|
3380bf8492 | ||
|
|
088911a5c8 | ||
|
|
096818feb2 | ||
|
|
070df3fef0 | ||
|
|
a2cae467fe | ||
|
|
61ec52c951 | ||
|
|
609e1213c3 | ||
|
|
d48bd013b7 | ||
|
|
ee25f00d85 | ||
|
|
e1636b3973 | ||
|
|
c9885fad86 | ||
|
|
139bab220c | ||
|
|
ba2665a9a8 | ||
|
|
74b7836937 | ||
|
|
3fcf811eab | ||
|
|
52616d09e6 | ||
|
|
c8f61690b4 | ||
|
|
8417f8c581 | ||
|
|
ee7bd686bd | ||
|
|
a254242b46 | ||
|
|
dd14b7e210 | ||
|
|
0fbd35627a | ||
|
|
198a53b7ed | ||
|
|
e703c3f431 | ||
|
|
d4130df421 | ||
|
|
76001c74db | ||
|
|
a6ce08d8b4 | ||
|
|
7c811469ab | ||
|
|
aca4c5a861 | ||
|
|
7c57d3a321 | ||
|
|
8f692b5c9e | ||
|
|
b8ab0c9657 | ||
|
|
dd096da7e6 | ||
|
|
a75c2e7324 | ||
|
|
fbbb26fab3 | ||
|
|
81e2c8bd4e | ||
|
|
8ac8eae692 | ||
|
|
dca4cd0038 | ||
|
|
3d1656efdb | ||
|
|
ea15c17832 | ||
|
|
84f489a73a | ||
|
|
6966a85e68 | ||
|
|
c06b1e3c59 | ||
|
|
92ccdc7fe5 | ||
|
|
fed39c6fdd | ||
|
|
c70d54d790 | ||
|
|
4e7dc77db9 | ||
|
|
dacaa14086 | ||
|
|
bd74e573b9 | ||
|
|
72e054a9b3 | ||
|
|
6875f11c35 | ||
|
|
ef6b1cc9f8 | ||
|
|
f0e82ba4af | ||
|
|
a2afe0794a | ||
|
|
af97063031 | ||
|
|
cd8509084b | ||
|
|
6345b78a44 | ||
|
|
ba6fd4ae83 | ||
|
|
1c943ff6a3 | ||
|
|
38d951437f | ||
|
|
84720809ba | ||
|
|
e40f4349a8 | ||
|
|
eaf432e3ad | ||
|
|
147aef6dc5 | ||
|
|
e3154f5824 | ||
|
|
12d7312853 | ||
|
|
a68fc52891 | ||
|
|
d8194b95cf | ||
|
|
0e856b7d80 | ||
|
|
d1af53fb1b | ||
|
|
ef8e2f32b3 | ||
|
|
913f28a5f6 | ||
|
|
62511ee9e3 | ||
|
|
83ef5b6d6f | ||
|
|
08184114ab | ||
|
|
fe1790b1f7 | ||
|
|
bcd6d20197 | ||
|
|
52376fadd0 | ||
|
|
6d28da7d64 | ||
|
|
afac0bdfc7 | ||
|
|
aa3e3941ed | ||
|
|
d7f666e323 | ||
|
|
cdfea5aba4 | ||
|
|
faaca5ae57 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ a.out
|
||||
docs/_*
|
||||
bin
|
||||
.idea
|
||||
dean*
|
||||
data/vagrant/.vagrant
|
||||
data/vagrant/vagrant_ansible_inventory_default
|
||||
|
||||
@@ -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
|
||||
|
||||
18
README.md
18
README.md
@@ -1,6 +1,8 @@
|
||||
# Rating system for Telecom & ISP environments #
|
||||
## Rating system for Telecom & ISP environments ##
|
||||
|
||||
## Features ##
|
||||
[](https://drone.io/github.com/cgrates/cgrates/latest) [](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: [](http://goci.me/project/github.com/cgrates/cgrates) [](http://travis-ci.org/cgrates/cgrates)
|
||||
|
||||
221
apier/accounts.go
Normal file
221
apier/accounts.go
Normal 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
648
apier/apier.go
Normal 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
1375
apier/apier_local_test.go
Normal file
File diff suppressed because it is too large
Load Diff
153
apier/cdre.go
Normal file
153
apier/cdre.go
Normal 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
45
apier/cdrs.go
Normal 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
42
apier/tp.go
Normal 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
104
apier/tpaccountactions.go
Normal 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
99
apier/tpactions.go
Normal 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
97
apier/tpactiontimings.go
Normal 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
95
apier/tpactiontriggers.go
Normal 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
|
||||
}
|
||||
92
apier/tpdestinationrates.go
Normal file
92
apier/tpdestinationrates.go
Normal 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
90
apier/tpdestinations.go
Normal 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
92
apier/tprates.go
Normal 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
92
apier/tpratingplans.go
Normal 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
105
apier/tpratingprofiles.go
Normal 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
94
apier/tptimings.go
Normal 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
|
||||
}
|
||||
285
apier/tutfscsv_local_test.go
Normal file
285
apier/tutfscsv_local_test.go
Normal 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))
|
||||
}()
|
||||
}
|
||||
504
apier/tutfsjson_local_test.go
Normal file
504
apier/tutfsjson_local_test.go
Normal 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))
|
||||
}()
|
||||
}
|
||||
6
build.sh
6
build.sh
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
261
cdrc/cdrc.go
Normal 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
155
cdrc/cdrc_local_test.go
Normal 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
108
cdrc/cdrc_test.go
Normal 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
28
cdre/cdrexporter.go
Normal 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
53
cdre/csv.go
Normal 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
47
cdre/csv_test.go
Normal 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
267
cdre/fixedwidth.go
Normal 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
190
cdre/fixedwidth_test.go
Normal 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
73
cdre/libfixedwidth.go
Normal 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
116
cdre/libfixedwidth_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
163
cdrs/cdrs.go
163
cdrs/cdrs.go
@@ -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
310
cdrs/fscdr.go
Normal 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
182
cdrs/fscdr_test.go
Normal file
File diff suppressed because one or more lines are too long
@@ -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))
|
||||
|
||||
}
|
||||
|
||||
473
cmd/cgr-engine/cgr-engine.go
Normal file
473
cmd/cgr-engine/cgr-engine.go
Normal 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!")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
3
cmd/cgr-tester/README.md
Normal 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.
|
||||
174
cmd/cgr-tester/cgr-tester.go
Normal file
174
cmd/cgr-tester/cgr-tester.go
Normal 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())
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
807
config/config.go
807
config/config.go
@@ -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
|
||||
}
|
||||
|
||||
47
config/config_local_test.go
Normal file
47
config/config_local_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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
66
config/helpers.go
Normal 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
39
config/helpers_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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
123
config/xmlconfig.go
Normal 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
119
config/xmlconfig_test.go
Normal 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
82
console/add_account.go
Normal 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
97
console/add_balance.go
Normal 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
|
||||
}
|
||||
90
console/add_triggeredaction.go
Normal file
90
console/add_triggeredaction.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
97
console/debit_balance.go
Normal 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
70
console/destination.go
Normal 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
75
console/execute_action.go
Normal 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
92
console/export_cdrs.go
Normal 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
74
console/get_account.go
Normal 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
67
console/get_cache_age.go
Normal 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
|
||||
}
|
||||
63
console/get_cache_stats.go
Normal file
63
console/get_cache_stats.go
Normal 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
76
console/get_callcost.go
Normal 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
97
console/get_cost.go
Normal 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
102
console/get_maxduration.go
Normal 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
64
console/reload_cache.go
Normal 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
|
||||
}
|
||||
63
console/reload_scheduler.go
Normal file
63
console/reload_scheduler.go
Normal 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
71
console/rem_cdrs.go
Normal 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
|
||||
}
|
||||
69
console/set_accountactions.go
Normal file
69
console/set_accountactions.go
Normal 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
|
||||
}
|
||||
69
console/set_ratingprofile.go
Normal file
69
console/set_ratingprofile.go
Normal 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
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
35
data/conf/samples/apier_local_test.cfg
Normal file
35
data/conf/samples/apier_local_test.cfg
Normal 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>.
|
||||
|
||||
|
||||
20
data/conf/samples/cgr_addconfig.xml
Normal file
20
data/conf/samples/cgr_addconfig.xml
Normal 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>
|
||||
13
data/conf/samples/config_local_test.cfg
Normal file
13
data/conf/samples/config_local_test.cfg
Normal 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>
|
||||
|
||||
20
data/conf/samples/mediator_test1.cfg
Normal file
20
data/conf/samples/mediator_test1.cfg
Normal 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>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
23
data/freeswitch/conf/autoload_configs/cdr_csv.conf.xml
Normal file
23
data/freeswitch/conf/autoload_configs/cdr_csv.conf.xml
Normal 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>
|
||||
|
||||
51
data/freeswitch/conf/autoload_configs/json_cdr.conf.xml
Normal file
51
data/freeswitch/conf/autoload_configs/json_cdr.conf.xml
Normal 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
9
data/monit/cgrates.monit
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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,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,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,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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user