mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Compare commits
488 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d622cbd14 | ||
|
|
3787ee64d5 | ||
|
|
cf02179cde | ||
|
|
35e120e3f6 | ||
|
|
5c36dbc62e | ||
|
|
0f95e986a7 | ||
|
|
616a81279e | ||
|
|
988f3c6bf2 | ||
|
|
53f19dad4a | ||
|
|
d1bd2700fd | ||
|
|
e45742761e | ||
|
|
410ecfdd43 | ||
|
|
36ae05cde2 | ||
|
|
b24c1982bf | ||
|
|
54e7c8a971 | ||
|
|
af4b38ffd5 | ||
|
|
6c6b27a9d9 | ||
|
|
75c8253756 | ||
|
|
f9204b54aa | ||
|
|
bba82e7c91 | ||
|
|
b287c82ec3 | ||
|
|
98996edc7e | ||
|
|
6342c4a3e5 | ||
|
|
c64dae59d4 | ||
|
|
29eff410e7 | ||
|
|
1ff2af86d4 | ||
|
|
514df9a489 | ||
|
|
0a6f0ab0c7 | ||
|
|
c0b18e3ae6 | ||
|
|
48d5866ea1 | ||
|
|
e9ba47beab | ||
|
|
99eb85f8bc | ||
|
|
71a932e343 | ||
|
|
33e3f3fd66 | ||
|
|
bb9e77c688 | ||
|
|
ac3bbfe5a2 | ||
|
|
6e94842539 | ||
|
|
b28b8a60dd | ||
|
|
88fdcbfca8 | ||
|
|
edb783ab39 | ||
|
|
90fa1465ac | ||
|
|
455379ef97 | ||
|
|
a48fa90dd8 | ||
|
|
a290a6aa6e | ||
|
|
795a799ac2 | ||
|
|
f2efe6fe9a | ||
|
|
4d78c0a63b | ||
|
|
97c4cf5bd6 | ||
|
|
bcf4c3d08e | ||
|
|
f72568054b | ||
|
|
cabcd67559 | ||
|
|
a6b82806fd | ||
|
|
7c34c36f6f | ||
|
|
f8bb5d4d66 | ||
|
|
bdc127b76d | ||
|
|
909ceb2759 | ||
|
|
5a080846e7 | ||
|
|
9adebc6b10 | ||
|
|
09acd19fb7 | ||
|
|
dd08456d13 | ||
|
|
b29abe35a4 | ||
|
|
b86a8c77ba | ||
|
|
9feb8c47db | ||
|
|
c63a9a2221 | ||
|
|
5496a0eae2 | ||
|
|
2411534ede | ||
|
|
3fcf8716e0 | ||
|
|
146ef48435 | ||
|
|
aade719f27 | ||
|
|
25d0715a70 | ||
|
|
7cbf5c2b56 | ||
|
|
2a953fcc33 | ||
|
|
d07d772013 | ||
|
|
7a9d28d8f5 | ||
|
|
3a09524aa0 | ||
|
|
0b7fcf2167 | ||
|
|
a1e562e248 | ||
|
|
39379bf64a | ||
|
|
6dfaca4443 | ||
|
|
452b794f6d | ||
|
|
605fde865a | ||
|
|
6634751d7a | ||
|
|
8d1be00962 | ||
|
|
a096d97d94 | ||
|
|
1574de1d38 | ||
|
|
3e0e30f2cc | ||
|
|
97977e98cf | ||
|
|
2527ad2970 | ||
|
|
4779842574 | ||
|
|
6092df2f76 | ||
|
|
ba3eb3dda4 | ||
|
|
9e6d6a7b07 | ||
|
|
0033f400b2 | ||
|
|
8b77f457d2 | ||
|
|
63eafcab20 | ||
|
|
fdde40d312 | ||
|
|
eef3a43016 | ||
|
|
8c54ad63e7 | ||
|
|
7ed24141b6 | ||
|
|
ac9b244eed | ||
|
|
b57fd3b09a | ||
|
|
6d03010858 | ||
|
|
67d6bdb4d8 | ||
|
|
ed098e4665 | ||
|
|
d6ede3560a | ||
|
|
d29512de02 | ||
|
|
ebc972ed41 | ||
|
|
98cdbb6dc0 | ||
|
|
26b1e8685b | ||
|
|
5400b2f44b | ||
|
|
1ae632ba80 | ||
|
|
bbb116b699 | ||
|
|
ee64e5a0df | ||
|
|
b2100a354a | ||
|
|
9c2925894b | ||
|
|
bd6ed7e66a | ||
|
|
f86ab09580 | ||
|
|
2250d9a2cc | ||
|
|
2c72e2e7c0 | ||
|
|
1474516d9a | ||
|
|
6742345026 | ||
|
|
91ac0fd3f2 | ||
|
|
7bff0db7d2 | ||
|
|
7abf8cb12c | ||
|
|
9c4d3439bb | ||
|
|
b012922272 | ||
|
|
8342a00fcb | ||
|
|
8ee8962328 | ||
|
|
1f28e2bc2c | ||
|
|
0cc9b0caff | ||
|
|
72d38127f9 | ||
|
|
f2da780893 | ||
|
|
4ea9d8f35a | ||
|
|
2bab5f0585 | ||
|
|
fc546fe6b6 | ||
|
|
7fff0b2aad | ||
|
|
a541d32642 | ||
|
|
1d1aa5d3e1 | ||
|
|
9b2b3d2c94 | ||
|
|
0f6e8540ef | ||
|
|
9cfe1a9864 | ||
|
|
6cb79b5585 | ||
|
|
dcb4fa77e0 | ||
|
|
96d1775dd1 | ||
|
|
a1d96d805e | ||
|
|
e93b8a6c6f | ||
|
|
19e994ca8a | ||
|
|
4f5894b27e | ||
|
|
36282cbc17 | ||
|
|
f092d517df | ||
|
|
1091f58297 | ||
|
|
8fe59a2a3f | ||
|
|
38b3dad5b0 | ||
|
|
c58684f99e | ||
|
|
48bbe1af89 | ||
|
|
3dc0d44f4b | ||
|
|
8a2adb5e61 | ||
|
|
8ac8b65a95 | ||
|
|
71464ce65e | ||
|
|
fd532fb1c3 | ||
|
|
5e75234ede | ||
|
|
f6d0454a81 | ||
|
|
1289adc155 | ||
|
|
a5bf017085 | ||
|
|
ca4988dd77 | ||
|
|
93ec058b19 | ||
|
|
ae78f8fba0 | ||
|
|
b8eb3fa513 | ||
|
|
5430490ec5 | ||
|
|
362f34fe21 | ||
|
|
982f6e0864 | ||
|
|
a42015a461 | ||
|
|
25da31a301 | ||
|
|
cb9e69f5c9 | ||
|
|
62d1a31290 | ||
|
|
3c4a247052 | ||
|
|
c08fc13629 | ||
|
|
a449194b62 | ||
|
|
5a1994a943 | ||
|
|
c92affde6d | ||
|
|
a722d3242b | ||
|
|
8aa5540d23 | ||
|
|
e6a6910a46 | ||
|
|
84dffb963a | ||
|
|
c44287bee0 | ||
|
|
8023de5f24 | ||
|
|
26fac17b33 | ||
|
|
430da9b714 | ||
|
|
77da8b0daf | ||
|
|
31a573fde4 | ||
|
|
e14e28b31c | ||
|
|
e09212354c | ||
|
|
5daf99f875 | ||
|
|
a62ab32bbc | ||
|
|
8c23a91f05 | ||
|
|
4b77ca7acd | ||
|
|
6a91c174c1 | ||
|
|
8017af8aab | ||
|
|
50750e2dc2 | ||
|
|
511fcfd8f5 | ||
|
|
14c613150f | ||
|
|
b439ff9f92 | ||
|
|
4ea3ac49da | ||
|
|
10374448e6 | ||
|
|
d1807386c6 | ||
|
|
28c27c8863 | ||
|
|
57110e81c8 | ||
|
|
c904343280 | ||
|
|
392071da85 | ||
|
|
bb102e65c2 | ||
|
|
e9da9fd565 | ||
|
|
c8c52842cb | ||
|
|
192ce121de | ||
|
|
d47e1bf53b | ||
|
|
e808168d52 | ||
|
|
22670e12a8 | ||
|
|
0986c1371b | ||
|
|
3a8d78e798 | ||
|
|
b5f26c00b8 | ||
|
|
090f7706e8 | ||
|
|
6d00cce055 | ||
|
|
7bdd49b649 | ||
|
|
03fa289a8d | ||
|
|
f77e6f03f3 | ||
|
|
a13ebf828c | ||
|
|
362d2d4b22 | ||
|
|
e96251aba3 | ||
|
|
31b421c755 | ||
|
|
9b37e97cc2 | ||
|
|
a014ef8401 | ||
|
|
a4b3d94431 | ||
|
|
03dd27b8d8 | ||
|
|
ff7f4cac09 | ||
|
|
38877ca8f9 | ||
|
|
2fef2248e1 | ||
|
|
a25fc94d45 | ||
|
|
4a21ed9684 | ||
|
|
8e505fdbd9 | ||
|
|
bcb9479ee5 | ||
|
|
fcecc823d0 | ||
|
|
704fae01ce | ||
|
|
1edd4f13d8 | ||
|
|
3d27154b91 | ||
|
|
0f5b998c82 | ||
|
|
056b9c6c61 | ||
|
|
d3835067cf | ||
|
|
222602dfff | ||
|
|
bc5a80fe25 | ||
|
|
c973ea99e2 | ||
|
|
ff714a1bc8 | ||
|
|
c8ce2ddce2 | ||
|
|
fe06ede121 | ||
|
|
196978bcec | ||
|
|
cc585cd86e | ||
|
|
dac4300726 | ||
|
|
6b1a67d4f5 | ||
|
|
c4a463606a | ||
|
|
4af3af33c5 | ||
|
|
4545ca1a30 | ||
|
|
f9f994cd7e | ||
|
|
3d99f917f5 | ||
|
|
b56356486f | ||
|
|
7bd66530fe | ||
|
|
8e7eb8233f | ||
|
|
f436346873 | ||
|
|
c063bc2a21 | ||
|
|
1e92a6795f | ||
|
|
18629002a6 | ||
|
|
b62d7ba52d | ||
|
|
7b2a452c4f | ||
|
|
e15a08d633 | ||
|
|
4f95f56284 | ||
|
|
42e9719fed | ||
|
|
31881ea41c | ||
|
|
e6aaf24a14 | ||
|
|
00248b16dc | ||
|
|
dbf6379818 | ||
|
|
93fa37b20b | ||
|
|
9bf5f7611b | ||
|
|
5fe65f203c | ||
|
|
ad4fedc197 | ||
|
|
201b7e7677 | ||
|
|
f0b2f240b1 | ||
|
|
cb33f01695 | ||
|
|
bbe64e3e30 | ||
|
|
5aef6595a2 | ||
|
|
1ad4068d73 | ||
|
|
d5f6df9473 | ||
|
|
66d9377c53 | ||
|
|
79237637e1 | ||
|
|
e8690fc23e | ||
|
|
aaeac3329a | ||
|
|
129ca0cd93 | ||
|
|
9bea2fa7d7 | ||
|
|
4d33d7cf81 | ||
|
|
2acd4377e9 | ||
|
|
8c29d041f1 | ||
|
|
a90356d62c | ||
|
|
040430bc3b | ||
|
|
d0d49481a2 | ||
|
|
d6299cd08d | ||
|
|
65b7f19871 | ||
|
|
7f747c4529 | ||
|
|
ec5b8711ad | ||
|
|
dd4d9ab87e | ||
|
|
4f4f3b1108 | ||
|
|
6536465e1f | ||
|
|
c3f8521ad4 | ||
|
|
e157966af2 | ||
|
|
0011e417c2 | ||
|
|
da6f9cad6a | ||
|
|
ec66859d34 | ||
|
|
0bfd104e12 | ||
|
|
f8cd44f792 | ||
|
|
79d654c2a0 | ||
|
|
6ffdb1633f | ||
|
|
23e2db7332 | ||
|
|
7bc0003fcf | ||
|
|
b54a8d7dd7 | ||
|
|
370fc31db8 | ||
|
|
ed3ef16c09 | ||
|
|
2b8b4f4791 | ||
|
|
a4c556a484 | ||
|
|
e893862f26 | ||
|
|
c879c7a4d5 | ||
|
|
6d0cbfe2aa | ||
|
|
456039d0a6 | ||
|
|
7a70b50bf4 | ||
|
|
dc7330b3c9 | ||
|
|
928adcfbf0 | ||
|
|
0ef211de7e | ||
|
|
9512291c49 | ||
|
|
e74a98fead | ||
|
|
f466fcbb6a | ||
|
|
f7abbacfe5 | ||
|
|
f45833edeb | ||
|
|
7bda45fcce | ||
|
|
95a8e8a217 | ||
|
|
ede467c899 | ||
|
|
fcdc0c03c7 | ||
|
|
b5af601163 | ||
|
|
7bd69607e4 | ||
|
|
0ebbb66dff | ||
|
|
5e114192b6 | ||
|
|
e5f66a8f4b | ||
|
|
9a63438577 | ||
|
|
45e798ce5d | ||
|
|
5aebe92788 | ||
|
|
e39187eab9 | ||
|
|
c9493005cd | ||
|
|
21176f7c88 | ||
|
|
24a2436f81 | ||
|
|
6f6f4c8c47 | ||
|
|
6497a09ae5 | ||
|
|
69f5921a8d | ||
|
|
5bd0816596 | ||
|
|
e01ad95465 | ||
|
|
35cb7483de | ||
|
|
f0095dbcb3 | ||
|
|
a365e90c1d | ||
|
|
509e3cde45 | ||
|
|
255fb4ea0e | ||
|
|
9d0f252aad | ||
|
|
f1731cccb7 | ||
|
|
393d9091b6 | ||
|
|
1d7d0b8078 | ||
|
|
402a1fbc68 | ||
|
|
12f91b009b | ||
|
|
48de7f3c30 | ||
|
|
a95fef496b | ||
|
|
25cb5be8c0 | ||
|
|
6526e52603 | ||
|
|
bc94fe34d1 | ||
|
|
330fb7b894 | ||
|
|
4934daa6b9 | ||
|
|
99faa0c112 | ||
|
|
4fdf80c633 | ||
|
|
575bcf2434 | ||
|
|
022e3a8c38 | ||
|
|
c790f59b58 | ||
|
|
2013c16b0e | ||
|
|
4265dff012 | ||
|
|
b779f09d9d | ||
|
|
61e81ced27 | ||
|
|
4ffc8555a1 | ||
|
|
1953bf4233 | ||
|
|
42718617b9 | ||
|
|
ddcd54db4b | ||
|
|
90ab47ade9 | ||
|
|
b1b3f4ab18 | ||
|
|
2a253892c5 | ||
|
|
b4dbb1ca9e | ||
|
|
251d504d40 | ||
|
|
bba4a878f0 | ||
|
|
2d042d65bf | ||
|
|
d3e5555b5c | ||
|
|
f8fec02ab5 | ||
|
|
83b7034b7f | ||
|
|
efe9e19460 | ||
|
|
1935d41b9d | ||
|
|
92d03eaf1a | ||
|
|
0e55acbfdb | ||
|
|
0122acced1 | ||
|
|
efc9240142 | ||
|
|
bcb9582d6b | ||
|
|
94da5d9e74 | ||
|
|
05e4772f6d | ||
|
|
4637c247fe | ||
|
|
27e8c34fcb | ||
|
|
6df17bf2e8 | ||
|
|
57b4226e8a | ||
|
|
43039b063b | ||
|
|
668352b860 | ||
|
|
2da2294c0f | ||
|
|
5f4580947a | ||
|
|
0192e5088f | ||
|
|
4cc6d248e4 | ||
|
|
e1c174bebb | ||
|
|
81b611f186 | ||
|
|
d08b77fcb7 | ||
|
|
341ead514e | ||
|
|
0236553c87 | ||
|
|
f1b639b04e | ||
|
|
4e1776d6fd | ||
|
|
5d9e7bae67 | ||
|
|
f2eade654c | ||
|
|
df655e7751 | ||
|
|
60075616b4 | ||
|
|
b958ee38aa | ||
|
|
31138857ee | ||
|
|
adc7c3d671 | ||
|
|
e303940d76 | ||
|
|
5f65388e69 | ||
|
|
5d29ce3d61 | ||
|
|
9fcf43f8b4 | ||
|
|
a7b71bae43 | ||
|
|
c11e5ae7e7 | ||
|
|
d6f601e9ab | ||
|
|
fc76d891b7 | ||
|
|
a3fff42338 | ||
|
|
b40c3d1653 | ||
|
|
9dcd7c0ef4 | ||
|
|
62148b6d06 | ||
|
|
1dca95a6d3 | ||
|
|
d624af2e94 | ||
|
|
6dd22fa847 | ||
|
|
2e6ee328c9 | ||
|
|
d5a8a2c292 | ||
|
|
7313e64362 | ||
|
|
564efade3d | ||
|
|
8fd5c112b1 | ||
|
|
c940d628a6 | ||
|
|
71f296d656 | ||
|
|
517bf7964b | ||
|
|
834a25ae74 | ||
|
|
a03b25cc45 | ||
|
|
e5dc9a2e56 | ||
|
|
ac86c6b60c | ||
|
|
cd29a3360b | ||
|
|
eb333fcfc0 | ||
|
|
3313e7c41b | ||
|
|
ac1e9992bf | ||
|
|
cc0534aa8b | ||
|
|
62c56ae26b | ||
|
|
a8e4dad5f0 | ||
|
|
8276ea73b7 | ||
|
|
cdd93e2161 | ||
|
|
46959aaa35 | ||
|
|
c96f48435b | ||
|
|
65d23a1eb5 | ||
|
|
9b1ba8b6c7 | ||
|
|
cbfcd1133d | ||
|
|
52a5fb918e | ||
|
|
8325c8e2e7 | ||
|
|
d8516d2662 | ||
|
|
d062a98938 | ||
|
|
7c37399757 | ||
|
|
611743d3c5 | ||
|
|
c8f4118780 | ||
|
|
fded831fad | ||
|
|
e20605471e | ||
|
|
ee6b1cd74f | ||
|
|
d95d842ede | ||
|
|
8987b11cbd | ||
|
|
26b9590fa0 | ||
|
|
4e42c69df6 | ||
|
|
21b80c992b | ||
|
|
56faf676dd |
@@ -2,6 +2,8 @@ language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
|
||||
script: $TRAVIS_BUILD_DIR/test.sh
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Rating system for Telecom & ISP environments ##
|
||||
## Real-time Charging System for Telecom & ISP environments ##
|
||||
|
||||
[](https://drone.io/github.com/cgrates/cgrates/latest) [](http://travis-ci.org/cgrates/cgrates)
|
||||
[](http://travis-ci.org/cgrates/cgrates)
|
||||
|
||||
### Features ###
|
||||
+ Rates for prepaid and for postpaid
|
||||
@@ -24,4 +24,6 @@ PDF, Epub, Manpage http://readthedocs.org/projects/cgrates/downloads/
|
||||
|
||||
API reference [godoc](http://godoc.org/github.com/cgrates/cgrates/apier)
|
||||
|
||||
Also check irc.freenode.net#cgrates and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support.
|
||||
Also check [irc.freenode.net #cgrates](irc://irc.freenode.net:6667/cgrates) ([Webchat](http://webchat.freenode.net?randomnick=1&channels=%23cgrates)) and [Google group](https://groups.google.com/forum/#!forum/cgrates) for a more real-time support.
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -51,7 +51,7 @@ func (self *ApierV1) GetAccountActionPlan(attrs AttrAcntAction, reply *[]*Accoun
|
||||
}
|
||||
for _, ats := range allATs {
|
||||
for _, at := range ats {
|
||||
if utils.IsSliceMember(at.AccountIds, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)) {
|
||||
if utils.IsSliceMember(at.AccountIds, utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction)) {
|
||||
accountATs = append(accountATs, &AccountActionTiming{Uuid: at.Uuid, ActionPlanId: at.Id, ActionsId: at.ActionsId, NextExecTime: at.GetNextStartTime(time.Now())})
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) e
|
||||
} else if len(ats) == 0 {
|
||||
return 0, errors.New(utils.ERR_NOT_FOUND)
|
||||
}
|
||||
ats = engine.RemActionTiming(ats, attrs.ActionTimingId, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction))
|
||||
ats = engine.RemActionTiming(ats, attrs.ActionTimingId, utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction))
|
||||
if err := self.AccountDb.SetActionTimings(attrs.ActionPlanId, ats); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func (self *ApierV1) GetAccountActionTriggers(attrs AttrAcntAction, reply *engin
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if balance, err := self.AccountDb.GetAccount(utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)); err != nil {
|
||||
if balance, err := self.AccountDb.GetAccount(utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction)); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else {
|
||||
*reply = balance.ActionTriggers
|
||||
@@ -128,7 +128,7 @@ func (self *ApierV1) RemAccountActionTriggers(attrs AttrRemAcntActionTriggers, r
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Direction"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
balanceId := utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)
|
||||
balanceId := utils.AccountKey(attrs.Tenant, attrs.Account, attrs.Direction)
|
||||
_, err := engine.AccLock.Guard(balanceId, func() (float64, error) {
|
||||
ub, err := self.AccountDb.GetAccount(balanceId)
|
||||
if err != nil {
|
||||
@@ -169,7 +169,7 @@ func (self *ApierV1) SetAccount(attr AttrSetAccount, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Direction", "Account"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
balanceId := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction)
|
||||
balanceId := utils.AccountKey(attr.Tenant, attr.Account, attr.Direction)
|
||||
var ub *engine.Account
|
||||
var ats engine.ActionPlan
|
||||
_, err := engine.AccLock.Guard(balanceId, func() (float64, error) {
|
||||
|
||||
137
apier/aliases.go
Normal file
137
apier/aliases.go
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
type AttrAddRatingSubjectAliases struct {
|
||||
Tenant, Subject string
|
||||
Aliases []string
|
||||
}
|
||||
|
||||
type AttrAddAccountAliases struct {
|
||||
Tenant, Account string
|
||||
Aliases []string
|
||||
}
|
||||
|
||||
// Retrieve aliases configured for a rating profile subject
|
||||
func (self *ApierV1) AddRatingSubjectAliases(attrs AttrAddRatingSubjectAliases, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Subject", "Aliases"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
aliasesChanged := []string{}
|
||||
for _, alias := range attrs.Aliases {
|
||||
if err := self.RatingDb.SetRpAlias(utils.RatingSubjectAliasKey(attrs.Tenant, alias), attrs.Subject); err != nil {
|
||||
return fmt.Errorf("%s:%s:%s", utils.ERR_SERVER_ERROR, alias, err.Error())
|
||||
}
|
||||
aliasesChanged = append(aliasesChanged, engine.RP_ALIAS_PREFIX+utils.RatingSubjectAliasKey(attrs.Tenant, alias))
|
||||
}
|
||||
didNotChange := []string{}
|
||||
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, didNotChange, aliasesChanged, didNotChange); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve aliases configured for a rating profile subject
|
||||
func (self *ApierV1) GetRatingSubjectAliases(attrs engine.TenantRatingSubject, reply *[]string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Subject"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if aliases, err := self.RatingDb.GetRPAliases(attrs.Tenant, attrs.Subject, false); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else if len(aliases) == 0 { // Need it since otherwise we get some unexpected errrors in the client
|
||||
return errors.New(utils.ERR_NOT_FOUND)
|
||||
} else {
|
||||
*reply = aliases
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve aliases configured for a rating profile subject
|
||||
func (self *ApierV1) RemRatingSubjectAliases(tenantRatingSubject engine.TenantRatingSubject, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&tenantRatingSubject, []string{"Tenant", "Subject"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if err := self.RatingDb.RemoveRpAliases([]*engine.TenantRatingSubject{&tenantRatingSubject}); err != nil {
|
||||
return fmt.Errorf("%s:% s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
didNotChange := []string{}
|
||||
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, didNotChange, nil, didNotChange); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ApierV1) AddAccountAliases(attrs AttrAddAccountAliases, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account", "Aliases"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
aliasesChanged := []string{}
|
||||
for _, alias := range attrs.Aliases {
|
||||
if err := self.AccountDb.SetAccAlias(utils.AccountAliasKey(attrs.Tenant, alias), attrs.Account); err != nil {
|
||||
return fmt.Errorf("%s:%s:%s", utils.ERR_SERVER_ERROR, alias, err.Error())
|
||||
}
|
||||
aliasesChanged = append(aliasesChanged, engine.ACC_ALIAS_PREFIX+utils.AccountAliasKey(attrs.Tenant, alias))
|
||||
}
|
||||
didNotChange := []string{}
|
||||
if err := self.AccountDb.CacheAccounting(didNotChange, didNotChange, aliasesChanged, didNotChange); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve aliases configured for an account
|
||||
func (self *ApierV1) GetAccountAliases(attrs engine.TenantAccount, reply *[]string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Account"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if aliases, err := self.AccountDb.GetAccountAliases(attrs.Tenant, attrs.Account, false); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else if len(aliases) == 0 {
|
||||
return errors.New(utils.ERR_NOT_FOUND)
|
||||
} else {
|
||||
*reply = aliases
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve aliases configured for a rating profile subject
|
||||
func (self *ApierV1) RemAccountAliases(tenantAccount engine.TenantAccount, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&tenantAccount, []string{"Tenant", "Account"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if err := self.AccountDb.RemoveAccAliases([]*engine.TenantAccount{&tenantAccount}); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
didNotChange := []string{}
|
||||
if err := self.AccountDb.CacheAccounting(didNotChange, didNotChange, nil, didNotChange); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
284
apier/apier.go
284
apier/apier.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package apier
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/cache2go"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
@@ -36,13 +36,15 @@ const (
|
||||
)
|
||||
|
||||
type ApierV1 struct {
|
||||
StorDb engine.LoadStorage
|
||||
RatingDb engine.RatingStorage
|
||||
AccountDb engine.AccountingStorage
|
||||
CdrDb engine.CdrStorage
|
||||
LogDb engine.LogStorage
|
||||
Sched *scheduler.Scheduler
|
||||
Config *config.CGRConfig
|
||||
StorDb engine.LoadStorage
|
||||
RatingDb engine.RatingStorage
|
||||
AccountDb engine.AccountingStorage
|
||||
CdrDb engine.CdrStorage
|
||||
LogDb engine.LogStorage
|
||||
Sched *scheduler.Scheduler
|
||||
Config *config.CGRConfig
|
||||
Responder *engine.Responder
|
||||
CdrStatsSrv *engine.Stats
|
||||
}
|
||||
|
||||
func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) error {
|
||||
@@ -54,6 +56,12 @@ func (self *ApierV1) GetDestination(dstId string, reply *engine.Destination) err
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrSetDestination struct { //ToDo
|
||||
Id string
|
||||
Prefixes []string
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
func (self *ApierV1) GetRatingPlan(rplnId string, reply *engine.RatingPlan) error {
|
||||
if rpln, err := self.RatingDb.GetRatingPlan(rplnId, false); err != nil {
|
||||
return errors.New(utils.ERR_NOT_FOUND)
|
||||
@@ -63,15 +71,8 @@ func (self *ApierV1) GetRatingPlan(rplnId string, reply *engine.RatingPlan) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrGetAccount struct {
|
||||
Tenant string
|
||||
Account string
|
||||
BalanceType string
|
||||
Direction string
|
||||
}
|
||||
|
||||
// Get balance
|
||||
func (self *ApierV1) GetAccount(attr *AttrGetAccount, reply *engine.Account) error {
|
||||
func (self *ApierV1) GetAccount(attr *utils.AttrGetAccount, reply *engine.Account) error {
|
||||
tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account)
|
||||
userBalance, err := self.AccountDb.GetAccount(tag)
|
||||
if err != nil {
|
||||
@@ -83,19 +84,25 @@ func (self *ApierV1) GetAccount(attr *AttrGetAccount, reply *engine.Account) err
|
||||
}
|
||||
|
||||
type AttrAddBalance struct {
|
||||
Tenant string
|
||||
Account string
|
||||
BalanceType string
|
||||
Direction string
|
||||
Value float64
|
||||
ExpirationDate time.Time
|
||||
RatingSubject string
|
||||
DestinationId string
|
||||
Weight float64
|
||||
Overwrite bool // When true it will reset if the balance is already there
|
||||
Tenant string
|
||||
Account string
|
||||
BalanceType string
|
||||
Direction string
|
||||
Value float64
|
||||
ExpiryTime string
|
||||
RatingSubject string
|
||||
DestinationId string
|
||||
Weight float64
|
||||
SharedGroup string
|
||||
Overwrite bool // When true it will reset if the balance is already there
|
||||
}
|
||||
|
||||
func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
|
||||
expTime, err := utils.ParseDate(attr.ExpiryTime)
|
||||
if err != nil {
|
||||
*reply = err.Error()
|
||||
return err
|
||||
}
|
||||
tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account)
|
||||
if _, err := self.AccountDb.GetAccount(tag); err != nil {
|
||||
// create user balance if not exists
|
||||
@@ -114,9 +121,12 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
|
||||
if attr.Direction == "" {
|
||||
attr.Direction = engine.OUTBOUND
|
||||
}
|
||||
aType := engine.TOPUP
|
||||
aType := engine.DEBIT
|
||||
// reverse the sign as it is a debit
|
||||
attr.Value = -attr.Value
|
||||
|
||||
if attr.Overwrite {
|
||||
aType = engine.TOPUP_RESET
|
||||
aType = engine.DEBIT_RESET
|
||||
}
|
||||
at.SetActions(engine.Actions{
|
||||
&engine.Action{
|
||||
@@ -125,10 +135,11 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error {
|
||||
Direction: attr.Direction,
|
||||
Balance: &engine.Balance{
|
||||
Value: attr.Value,
|
||||
ExpirationDate: attr.ExpirationDate,
|
||||
RateSubject: attr.RatingSubject,
|
||||
ExpirationDate: expTime,
|
||||
RatingSubject: attr.RatingSubject,
|
||||
DestinationId: attr.DestinationId,
|
||||
Weight: attr.Weight,
|
||||
SharedGroup: attr.SharedGroup,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -180,7 +191,7 @@ func (self *ApierV1) LoadRatingPlan(attrs AttrLoadRatingPlan, reply *string) err
|
||||
}
|
||||
//Automatic cache of the newly inserted rating plan
|
||||
didNotChange := []string{}
|
||||
if err := self.RatingDb.CacheRating(nil, nil, didNotChange, didNotChange); err != nil {
|
||||
if err := self.RatingDb.CacheRating(nil, nil, didNotChange, didNotChange, didNotChange); err != nil {
|
||||
return err
|
||||
}
|
||||
*reply = OK
|
||||
@@ -198,7 +209,7 @@ func (self *ApierV1) LoadRatingProfile(attrs utils.TPRatingProfile, reply *strin
|
||||
}
|
||||
//Automatic cache of the newly inserted rating profile
|
||||
didNotChange := []string{}
|
||||
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + attrs.KeyId()}, didNotChange); err != nil {
|
||||
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + attrs.KeyId()}, didNotChange, didNotChange); err != nil {
|
||||
return err
|
||||
}
|
||||
*reply = OK
|
||||
@@ -207,7 +218,7 @@ func (self *ApierV1) LoadRatingProfile(attrs utils.TPRatingProfile, reply *strin
|
||||
|
||||
type AttrSetRatingProfile struct {
|
||||
Tenant string // Tenant's Id
|
||||
TOR string // TypeOfRecord
|
||||
Category string // TypeOfRecord
|
||||
Direction string // Traffic direction, OUT is the only one supported for now
|
||||
Subject string // Rating subject, usually the same as account
|
||||
Overwrite bool // Overwrite if exists
|
||||
@@ -224,7 +235,7 @@ func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string)
|
||||
return fmt.Errorf("%s:RatingPlanActivation:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
}
|
||||
tpRpf := utils.TPRatingProfile{Tenant: attrs.Tenant, TOR: attrs.TOR, Direction: attrs.Direction, Subject: attrs.Subject}
|
||||
tpRpf := utils.TPRatingProfile{Tenant: attrs.Tenant, Category: attrs.Category, Direction: attrs.Direction, Subject: attrs.Subject}
|
||||
keyId := tpRpf.KeyId()
|
||||
if !attrs.Overwrite {
|
||||
if exists, err := self.RatingDb.HasData(engine.RATING_PROFILE_PREFIX, keyId); err != nil {
|
||||
@@ -245,14 +256,14 @@ func (self *ApierV1) SetRatingProfile(attrs AttrSetRatingProfile, reply *string)
|
||||
return fmt.Errorf(fmt.Sprintf("%s:RatingPlanId:%s", utils.ERR_NOT_FOUND, ra.RatingPlanId))
|
||||
}
|
||||
rpfl.RatingPlanActivations[idx] = &engine.RatingPlanActivation{ActivationTime: at, RatingPlanId: ra.RatingPlanId,
|
||||
FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.TOR, ra.FallbackSubjects)}
|
||||
FallbackKeys: utils.FallbackSubjKeys(tpRpf.Direction, tpRpf.Tenant, tpRpf.Category, ra.FallbackSubjects)}
|
||||
}
|
||||
if err := self.RatingDb.SetRatingProfile(rpfl); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
//Automatic cache of the newly inserted rating profile
|
||||
didNotChange := []string{}
|
||||
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + keyId}, didNotChange); err != nil {
|
||||
if err := self.RatingDb.CacheRating(didNotChange, didNotChange, []string{engine.RATING_PROFILE_PREFIX + keyId}, didNotChange, didNotChange); err != nil {
|
||||
return err
|
||||
}
|
||||
*reply = OK
|
||||
@@ -300,7 +311,8 @@ func (self *ApierV1) SetActions(attrs AttrSetActions, reply *string) error {
|
||||
Value: apiAct.Units,
|
||||
Weight: apiAct.BalanceWeight,
|
||||
DestinationId: apiAct.DestinationId,
|
||||
RateSubject: apiAct.RatingSubject,
|
||||
RatingSubject: apiAct.RatingSubject,
|
||||
SharedGroup: apiAct.SharedGroup,
|
||||
},
|
||||
}
|
||||
storeActions[idx] = a
|
||||
@@ -308,10 +320,43 @@ func (self *ApierV1) SetActions(attrs AttrSetActions, reply *string) error {
|
||||
if err := self.AccountDb.SetActions(attrs.ActionsId, storeActions); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
didNotChange := []string{}
|
||||
self.AccountDb.CacheAccounting(nil, didNotChange, didNotChange, didNotChange)
|
||||
*reply = OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieves actions attached to specific ActionsId within cache
|
||||
func (self *ApierV1) GetActions(actsId string, reply *[]*utils.TPAction) error {
|
||||
if len(actsId) == 0 {
|
||||
return fmt.Errorf("%s:ActionsId", utils.ERR_MANDATORY_IE_MISSING, actsId)
|
||||
}
|
||||
acts := make([]*utils.TPAction, 0)
|
||||
engActs, err := self.AccountDb.GetActions(actsId, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
for _, engAct := range engActs {
|
||||
act := &utils.TPAction{Identifier: engAct.ActionType,
|
||||
BalanceType: engAct.BalanceType,
|
||||
Direction: engAct.Direction,
|
||||
ExpiryTime: engAct.ExpirationString,
|
||||
ExtraParameters: engAct.ExtraParameters,
|
||||
Weight: engAct.Weight,
|
||||
}
|
||||
if engAct.Balance != nil {
|
||||
act.Units = engAct.Balance.Value
|
||||
act.DestinationId = engAct.Balance.DestinationId
|
||||
act.RatingSubject = engAct.Balance.RatingSubject
|
||||
act.SharedGroup = engAct.Balance.SharedGroup
|
||||
act.BalanceWeight = engAct.Balance.Weight
|
||||
}
|
||||
acts = append(acts, act)
|
||||
}
|
||||
*reply = acts
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrSetActionPlan struct {
|
||||
Id string // Profile id
|
||||
ActionPlan []*ApiActionTiming // Set of actions this Actions profile will perform
|
||||
@@ -383,36 +428,45 @@ func (self *ApierV1) SetActionPlan(attrs AttrSetActionPlan, reply *string) error
|
||||
}
|
||||
|
||||
type AttrAddActionTrigger struct {
|
||||
Tenant string
|
||||
Account string
|
||||
Direction string
|
||||
BalanceType string
|
||||
ThresholdType string
|
||||
ThresholdValue float64
|
||||
DestinationId string
|
||||
Weight float64
|
||||
ActionsId string
|
||||
Tenant string
|
||||
Account string
|
||||
Direction string
|
||||
BalanceType string
|
||||
ThresholdType string
|
||||
ThresholdValue float64
|
||||
DestinationId string
|
||||
BalanceRatingSubject string //ToDo
|
||||
BalanceWeight float64
|
||||
BalanceExpiryTime string
|
||||
BalanceSharedGroup string //ToDo
|
||||
Weight float64
|
||||
ActionsId string
|
||||
}
|
||||
|
||||
func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string) error {
|
||||
if attr.Direction == "" {
|
||||
attr.Direction = engine.OUTBOUND
|
||||
}
|
||||
|
||||
balExpiryTime, err := utils.ParseTimeDetectLayout(attr.BalanceExpiryTime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
at := &engine.ActionTrigger{
|
||||
Id: utils.GenUUID(),
|
||||
BalanceType: attr.BalanceType,
|
||||
Direction: attr.Direction,
|
||||
ThresholdType: attr.ThresholdType,
|
||||
ThresholdValue: attr.ThresholdValue,
|
||||
DestinationId: attr.DestinationId,
|
||||
Weight: attr.Weight,
|
||||
ActionsId: attr.ActionsId,
|
||||
Executed: false,
|
||||
Id: utils.GenUUID(),
|
||||
BalanceType: attr.BalanceType,
|
||||
Direction: attr.Direction,
|
||||
ThresholdType: attr.ThresholdType,
|
||||
ThresholdValue: attr.ThresholdValue,
|
||||
DestinationId: attr.DestinationId,
|
||||
BalanceWeight: attr.BalanceWeight,
|
||||
BalanceExpirationDate: balExpiryTime,
|
||||
Weight: attr.Weight,
|
||||
ActionsId: attr.ActionsId,
|
||||
Executed: false,
|
||||
}
|
||||
|
||||
tag := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction)
|
||||
_, err := engine.AccLock.Guard(tag, func() (float64, error) {
|
||||
tag := utils.AccountKey(attr.Tenant, attr.Account, attr.Direction)
|
||||
_, err = engine.AccLock.Guard(tag, func() (float64, error) {
|
||||
userBalance, err := self.AccountDb.GetAccount(tag)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -433,6 +487,69 @@ func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrResetTriggeredAction struct {
|
||||
Tenant string
|
||||
Account string
|
||||
Direction string
|
||||
BalanceType string
|
||||
ThresholdType string
|
||||
ThresholdValue float64
|
||||
DestinationId string
|
||||
BalanceWeight float64
|
||||
BalanceRatingSubject string
|
||||
BalanceSharedGroup string
|
||||
}
|
||||
|
||||
func (self *ApierV1) ResetTriggeredActions(attr AttrResetTriggeredAction, reply *string) error {
|
||||
if attr.Direction == "" {
|
||||
attr.Direction = engine.OUTBOUND
|
||||
}
|
||||
extraParameters, err := json.Marshal(struct {
|
||||
ThresholdType string
|
||||
ThresholdValue float64
|
||||
DestinationId string
|
||||
BalanceWeight float64
|
||||
BalanceRatingSubject string
|
||||
BalanceSharedGroup string
|
||||
}{
|
||||
attr.ThresholdType,
|
||||
attr.ThresholdValue,
|
||||
attr.DestinationId,
|
||||
attr.BalanceWeight,
|
||||
attr.BalanceRatingSubject,
|
||||
attr.BalanceSharedGroup,
|
||||
})
|
||||
if err != nil {
|
||||
*reply = err.Error()
|
||||
return err
|
||||
}
|
||||
a := &engine.Action{
|
||||
BalanceType: attr.BalanceType,
|
||||
Direction: attr.Direction,
|
||||
ExtraParameters: string(extraParameters),
|
||||
}
|
||||
accID := utils.AccountKey(attr.Tenant, attr.Account, attr.Direction)
|
||||
_, err = engine.AccLock.Guard(accID, func() (float64, error) {
|
||||
acc, err := self.AccountDb.GetAccount(accID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
acc.ResetActionTriggers(a)
|
||||
|
||||
if err = self.AccountDb.SetAccount(acc); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, nil
|
||||
})
|
||||
if err != nil {
|
||||
*reply = err.Error()
|
||||
return err
|
||||
}
|
||||
*reply = OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process dependencies and load a specific AccountActions profile from storDb into dataDb.
|
||||
func (self *ApierV1) LoadAccountActions(attrs utils.TPAccountActions, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Account", "Direction"}); len(missing) != 0 {
|
||||
@@ -450,7 +567,7 @@ func (self *ApierV1) LoadAccountActions(attrs utils.TPAccountActions, reply *str
|
||||
}
|
||||
// ToDo: Get the action keys loaded by dbReader so we reload only these in cache
|
||||
// Need to do it before scheduler otherwise actions to run will be unknown
|
||||
if err := self.AccountDb.CacheAccounting(nil, nil, nil); err != nil {
|
||||
if err := self.AccountDb.CacheAccounting(nil, nil, nil, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if self.Sched != nil {
|
||||
@@ -473,7 +590,7 @@ func (self *ApierV1) ReloadScheduler(input string, reply *string) error {
|
||||
}
|
||||
|
||||
func (self *ApierV1) ReloadCache(attrs utils.ApiReloadCache, reply *string) error {
|
||||
var dstKeys, rpKeys, rpfKeys, actKeys, shgKeys, rpAlsKeys, accAlsKeys []string
|
||||
var dstKeys, rpKeys, rpfKeys, actKeys, shgKeys, rpAlsKeys, accAlsKeys, lcrKeys, dcsKeys []string
|
||||
if len(attrs.DestinationIds) > 0 {
|
||||
dstKeys = make([]string, len(attrs.DestinationIds))
|
||||
for idx, dId := range attrs.DestinationIds {
|
||||
@@ -516,10 +633,23 @@ func (self *ApierV1) ReloadCache(attrs utils.ApiReloadCache, reply *string) erro
|
||||
accAlsKeys[idx] = engine.ACC_ALIAS_PREFIX + alias
|
||||
}
|
||||
}
|
||||
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys); err != nil {
|
||||
if len(attrs.LCRIds) > 0 {
|
||||
lcrKeys = make([]string, len(attrs.LCRIds))
|
||||
for idx, lcrId := range attrs.LCRIds {
|
||||
lcrKeys[idx] = engine.LCR_PREFIX + lcrId
|
||||
}
|
||||
}
|
||||
|
||||
if len(attrs.DerivedChargers) > 0 {
|
||||
dcsKeys = make([]string, len(attrs.DerivedChargers))
|
||||
for idx, dc := range attrs.DerivedChargers {
|
||||
dcsKeys[idx] = engine.DERIVEDCHARGERS_PREFIX + dc
|
||||
}
|
||||
}
|
||||
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys, lcrKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys); err != nil {
|
||||
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys, dcsKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
*reply = "OK"
|
||||
@@ -535,6 +665,7 @@ func (self *ApierV1) GetCacheStats(attrs utils.AttrCacheStats, reply *utils.Cach
|
||||
cs.SharedGroups = cache2go.CountEntries(engine.SHARED_GROUP_PREFIX)
|
||||
cs.RatingAliases = cache2go.CountEntries(engine.RP_ALIAS_PREFIX)
|
||||
cs.AccountAliases = cache2go.CountEntries(engine.ACC_ALIAS_PREFIX)
|
||||
cs.DerivedChargers = cache2go.CountEntries(engine.DERIVEDCHARGERS_PREFIX)
|
||||
*reply = *cs
|
||||
return nil
|
||||
}
|
||||
@@ -583,10 +714,13 @@ func (self *ApierV1) LoadTariffPlanFromFolder(attrs utils.AttrLoadTpFromFolder,
|
||||
path.Join(attrs.FolderPath, utils.RATING_PLANS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.RATING_PROFILES_CSV),
|
||||
path.Join(attrs.FolderPath, utils.SHARED_GROUPS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.LCRS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.ACTIONS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.ACTION_PLANS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.ACTION_TRIGGERS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.ACCOUNT_ACTIONS_CSV))
|
||||
path.Join(attrs.FolderPath, utils.ACCOUNT_ACTIONS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.DERIVED_CHARGERS_CSV),
|
||||
path.Join(attrs.FolderPath, utils.CDR_STATS_CSV))
|
||||
if err := loader.LoadAll(); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
@@ -633,16 +767,32 @@ func (self *ApierV1) LoadTariffPlanFromFolder(attrs utils.AttrLoadTpFromFolder,
|
||||
for idx, alias := range accAliases {
|
||||
accAlsKeys[idx] = engine.ACC_ALIAS_PREFIX + alias
|
||||
}
|
||||
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys); err != nil {
|
||||
lcrIds, _ := loader.GetLoadedIds(engine.LCR_PREFIX)
|
||||
lcrKeys := make([]string, len(lcrIds))
|
||||
for idx, lcrId := range lcrIds {
|
||||
lcrKeys[idx] = engine.LCR_PREFIX + lcrId
|
||||
}
|
||||
dcs, _ := loader.GetLoadedIds(engine.DERIVEDCHARGERS_PREFIX)
|
||||
dcsKeys := make([]string, len(dcs))
|
||||
for idx, dc := range dcs {
|
||||
dcsKeys[idx] = engine.DERIVEDCHARGERS_PREFIX + dc
|
||||
}
|
||||
if err := self.RatingDb.CacheRating(dstKeys, rpKeys, rpfKeys, rpAlsKeys, lcrKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys); err != nil {
|
||||
if err := self.AccountDb.CacheAccounting(actKeys, shgKeys, accAlsKeys, dcsKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
if self.Sched != nil {
|
||||
self.Sched.LoadActionTimings(self.AccountDb)
|
||||
self.Sched.Restart()
|
||||
}
|
||||
cstKeys, _ := loader.GetLoadedIds(engine.CDR_STATS_PREFIX)
|
||||
if len(cstKeys) != 0 && self.CdrStatsSrv != nil {
|
||||
if err := self.CdrStatsSrv.ReloadQueues(cstKeys, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*reply = "OK"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -66,17 +66,20 @@ var waitRater = flag.Int("wait_rater", 500, "Number of miliseconds to wait for r
|
||||
|
||||
func init() {
|
||||
cfgPath = path.Join(*dataDir, "conf", "samples", "apier_local_test.cfg")
|
||||
cfg, _ = config.NewCGRConfig(&cfgPath)
|
||||
cfg, _ = config.NewCGRConfigFromFile(&cfgPath)
|
||||
}
|
||||
|
||||
func TestCreateDirs(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
for _, pathDir := range []string{cfg.CdreDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
|
||||
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
|
||||
if err := os.RemoveAll(pathDir); err != nil {
|
||||
t.Fatal("Error removing folder: ", pathDir, err)
|
||||
}
|
||||
if err := os.MkdirAll(pathDir, 0755); err != nil {
|
||||
t.Fatal("Error creating folder: ", pathDir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +97,7 @@ func TestCreateTables(t *testing.T) {
|
||||
} else {
|
||||
mysql = d.(*engine.MySQLStorage)
|
||||
}
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL,
|
||||
engine.CREATE_TARIFFPLAN_TABLES_SQL} {
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
|
||||
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
|
||||
t.Fatal("Error on mysql creation: ", err.Error())
|
||||
return // No point in going further
|
||||
@@ -286,7 +288,7 @@ func TestApierTPRate(t *testing.T) {
|
||||
}
|
||||
reply := ""
|
||||
rt := &utils.TPRate{TPid: engine.TEST_SQL, RateId: "RT_FS_USERS", RateSlots: []*utils.RateSlot{
|
||||
&utils.RateSlot{ConnectFee: 0, Rate: 0, RateUnit: "60s", RateIncrement: "60s", GroupIntervalStart: "0s", RoundingMethod: "*up", RoundingDecimals: 0},
|
||||
&utils.RateSlot{ConnectFee: 0, Rate: 0, RateUnit: "60s", RateIncrement: "60s", GroupIntervalStart: "0s"},
|
||||
}}
|
||||
rt2 := new(utils.TPRate)
|
||||
*rt2 = *rt
|
||||
@@ -340,10 +342,10 @@ func TestApierTPDestinationRate(t *testing.T) {
|
||||
}
|
||||
reply := ""
|
||||
dr := &utils.TPDestinationRate{TPid: engine.TEST_SQL, DestinationRateId: "DR_FREESWITCH_USERS", DestinationRates: []*utils.DestinationRate{
|
||||
&utils.DestinationRate{DestinationId: "FS_USERS", RateId: "RT_FS_USERS"},
|
||||
&utils.DestinationRate{DestinationId: "FS_USERS", RateId: "RT_FS_USERS", RoundingMethod: "*up", RoundingDecimals: 2},
|
||||
}}
|
||||
drDe := &utils.TPDestinationRate{TPid: engine.TEST_SQL, DestinationRateId: "DR_FREESWITCH_USERS", DestinationRates: []*utils.DestinationRate{
|
||||
&utils.DestinationRate{DestinationId: "GERMANY_MOBILE", RateId: "RT_FS_USERS"},
|
||||
&utils.DestinationRate{DestinationId: "GERMANY_MOBILE", RateId: "RT_FS_USERS", RoundingMethod: "*up", RoundingDecimals: 2},
|
||||
}}
|
||||
dr2 := new(utils.TPDestinationRate)
|
||||
*dr2 = *dr
|
||||
@@ -450,7 +452,7 @@ func TestApierTPRatingProfile(t *testing.T) {
|
||||
return
|
||||
}
|
||||
reply := ""
|
||||
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", TOR: "call", Direction: "*out", Subject: "*any",
|
||||
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", Category: "call", Direction: "*out", Subject: "*any",
|
||||
RatingPlanActivations: []*utils.TPRatingActivation{
|
||||
&utils.TPRatingActivation{ActivationTime: "2012-01-01T00:00:00Z", RatingPlanId: "RETAIL1", FallbackSubjects: ""},
|
||||
}}
|
||||
@@ -473,7 +475,7 @@ func TestApierTPRatingProfile(t *testing.T) {
|
||||
// Check missing params
|
||||
if err := rater.Call("ApierV1.SetTPRatingProfile", new(utils.TPRatingProfile), &reply); err == nil {
|
||||
t.Error("Calling ApierV1.SetTPRatingProfile, expected error, received: ", reply)
|
||||
} else if err.Error() != "MANDATORY_IE_MISSING:[TPid LoadId Tenant TOR Direction Subject RatingPlanActivations]" {
|
||||
} else if err.Error() != "MANDATORY_IE_MISSING:[TPid LoadId Tenant Category Direction Subject RatingPlanActivations]" {
|
||||
t.Error("Calling ApierV1.SetTPRatingProfile got unexpected error: ", err.Error())
|
||||
}
|
||||
// Test get
|
||||
@@ -743,7 +745,7 @@ func TestApierSetRatingProfile(t *testing.T) {
|
||||
}
|
||||
reply := ""
|
||||
rpa := &utils.TPRatingActivation{ActivationTime: "2012-01-01T00:00:00Z", RatingPlanId: "RETAIL1", FallbackSubjects: "dan2"}
|
||||
rpf := &AttrSetRatingProfile{Tenant: "cgrates.org", TOR: "call", Direction: "*out", Subject: "dan", RatingPlanActivations: []*utils.TPRatingActivation{rpa}}
|
||||
rpf := &AttrSetRatingProfile{Tenant: "cgrates.org", Category: "call", Direction: "*out", Subject: "dan", RatingPlanActivations: []*utils.TPRatingActivation{rpa}}
|
||||
if err := rater.Call("ApierV1.SetRatingProfile", rpf, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.SetRatingProfile: ", err.Error())
|
||||
} else if reply != "OK" {
|
||||
@@ -759,15 +761,15 @@ func TestApierSetRatingProfile(t *testing.T) {
|
||||
tStart, _ := utils.ParseDate("2013-08-07T17:30:00Z")
|
||||
tEnd, _ := utils.ParseDate("2013-08-07T17:31:30Z")
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
TOR: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "dan",
|
||||
Account: "dan",
|
||||
Destination: "+4917621621391",
|
||||
CallDuration: 90,
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "dan",
|
||||
Account: "dan",
|
||||
Destination: "+4917621621391",
|
||||
DurationIndex: 90,
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
}
|
||||
var cc engine.CallCost
|
||||
// Simple test that command is executed without errors
|
||||
@@ -784,7 +786,7 @@ func TestApierLoadRatingProfile(t *testing.T) {
|
||||
return
|
||||
}
|
||||
reply := ""
|
||||
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", TOR: "call", Direction: "*out", Subject: "*any"}
|
||||
rpf := &utils.TPRatingProfile{TPid: engine.TEST_SQL, LoadId: engine.TEST_SQL, Tenant: "cgrates.org", Category: "call", Direction: "*out", Subject: "*any"}
|
||||
if err := rater.Call("ApierV1.LoadRatingProfile", rpf, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.LoadRatingProfile: ", err.Error())
|
||||
} else if reply != "OK" {
|
||||
@@ -891,7 +893,6 @@ func TestApierGetRatingPlan(t *testing.T) {
|
||||
}
|
||||
reply := new(engine.RatingPlan)
|
||||
rplnId := "RETAIL1"
|
||||
//{"Id":"RETAIL1","Timings":{"96c78ff5":{"Years":[],"Months":[],"MonthDays":[],"WeekDays":[],"StartTime":"00:00:00","EndTime":""}},"Ratings":{"e41ffcf2":{"ConnectFee":0,"Rates":[{"GroupIntervalStart":0,"Value":0,"RateIncrement":60000000000,"RateUnit":60000000000}],"RoundingMethod":"*up","RoundingDecimals":0}},"DestinationRates":{"FS_USERS":[{"Timing":"96c78ff5","Rating":"e41ffcf2","Weight":10}],"GERMANY_MOBILE":[{"Timing":"96c78ff5","Rating":"e41ffcf2","Weight":10}]}
|
||||
if err := rater.Call("ApierV1.GetRatingPlan", rplnId, reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetRatingPlan: ", err.Error())
|
||||
}
|
||||
@@ -902,21 +903,13 @@ func TestApierGetRatingPlan(t *testing.T) {
|
||||
if len(reply.Timings) != 1 || len(reply.Ratings) != 1 {
|
||||
t.Error("Unexpected number of items received")
|
||||
}
|
||||
/*
|
||||
riTiming := &engine.RITiming{StartTime: "00:00:00"}
|
||||
for _, tm := range reply.Timings { // We only get one loop
|
||||
if !reflect.DeepEqual(tm, riTiming) {
|
||||
t.Errorf("Unexpected timings value: %v, expecting: %v", tm, riTiming)
|
||||
}
|
||||
}
|
||||
*/
|
||||
riRate := &engine.RIRate{Id: "RT_FS_USERS", ConnectFee: 0, RoundingMethod: "*up", RoundingDecimals: 0, Rates: []*engine.Rate{
|
||||
riRate := &engine.RIRate{ConnectFee: 0, RoundingMethod: "*up", RoundingDecimals: 2, Rates: []*engine.Rate{
|
||||
&engine.Rate{GroupIntervalStart: 0, Value: 0, RateIncrement: time.Duration(60) * time.Second, RateUnit: time.Duration(60) * time.Second},
|
||||
}}
|
||||
for _, rating := range reply.Ratings {
|
||||
riRateJsson, _ := json.Marshal(rating)
|
||||
riRateJson, _ := json.Marshal(rating)
|
||||
if !reflect.DeepEqual(rating, riRate) {
|
||||
t.Errorf("Unexpected riRate received: %s", riRateJsson)
|
||||
t.Errorf("Unexpected riRate received: %s", riRateJson)
|
||||
// {"Id":"RT_FS_USERS","ConnectFee":0,"Rates":[{"GroupIntervalStart":0,"Value":0,"RateIncrement":60000000000,"RateUnit":60000000000}],"RoundingMethod":"*up","RoundingDecimals":0}
|
||||
}
|
||||
}
|
||||
@@ -994,7 +987,7 @@ func TestApierExecuteAction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApierSetActions(t *testing.T) {
|
||||
func TestLocalApierSetActions(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
@@ -1012,6 +1005,21 @@ func TestApierSetActions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalApierGetActions(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
expectActs := []*utils.TPAction{
|
||||
&utils.TPAction{Identifier: engine.TOPUP_RESET, BalanceType: engine.CREDIT, Direction: engine.OUTBOUND, Units: 75.0, ExpiryTime: engine.UNLIMITED, Weight: 20.0}}
|
||||
|
||||
var reply []*utils.TPAction
|
||||
if err := rater.Call("ApierV1.GetActions", "ACTS_1", &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetActions: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expectActs, reply) {
|
||||
t.Errorf("Expected: %v, received: %v", expectActs, reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApierSetActionPlan(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
@@ -1162,35 +1170,35 @@ func TestApierGetAccount(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var reply *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 11.5 { // We expect 11.5 since we have added in the previous test 1.5
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 11.5 { // We expect 11.5 since we have added in the previous test 1.5
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 11.5, received: %f", reply)
|
||||
}
|
||||
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 1.5 {
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 1.5 {
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 1.5, received: %f", reply)
|
||||
}
|
||||
// The one we have topped up though executeAction
|
||||
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan2", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan2", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10 {
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10 {
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 10, received: %f", reply)
|
||||
}
|
||||
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan3", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan3", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 3.6 {
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 3.6 {
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 3.6, received: %f", reply)
|
||||
}
|
||||
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "dan6", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "dan6", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 1 {
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 1 {
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 1, received: %f", reply)
|
||||
}
|
||||
}
|
||||
@@ -1252,7 +1260,29 @@ func TestApierLoadTariffPlanFromFolder(t *testing.T) {
|
||||
} else if reply != "OK" {
|
||||
t.Error("Calling ApierV1.LoadTariffPlanFromFolder got reply: ", reply)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond) // Give time for scheduler to execute topups
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestResetDataAfterLoadFromFolder(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
reply := ""
|
||||
arc := new(utils.ApiReloadCache)
|
||||
// Simple test that command is executed without errors
|
||||
if err := rater.Call("ApierV1.ReloadCache", arc, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.ReloadCache: ", err.Error())
|
||||
} else if reply != "OK" {
|
||||
t.Error("Calling ApierV1.ReloadCache got reply: ", reply)
|
||||
}
|
||||
var rcvStats *utils.CacheStats
|
||||
expectedStats := &utils.CacheStats{Destinations: 4, RatingPlans: 3, RatingProfiles: 3, Actions: 5, DerivedChargers: 2}
|
||||
var args utils.AttrCacheStats
|
||||
if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
|
||||
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
|
||||
} else if !reflect.DeepEqual(rcvStats, expectedStats) {
|
||||
t.Errorf("Calling ApierV1.GetCacheStats received: %v, expected: %v", rcvStats, expectedStats)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure balance was topped-up
|
||||
@@ -1262,11 +1292,11 @@ func TestApierGetAccountAfterLoad(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var reply *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 11 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 11, received: %f", reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 11 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 11, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1278,15 +1308,15 @@ func TestResponderGetCost(t *testing.T) {
|
||||
tStart, _ := utils.ParseDate("2013-08-07T17:30:00Z")
|
||||
tEnd, _ := utils.ParseDate("2013-08-07T17:31:30Z")
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
TOR: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "+4917621621391",
|
||||
CallDuration: 90,
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "+4917621621391",
|
||||
DurationIndex: 90,
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
}
|
||||
var cc engine.CallCost
|
||||
// Simple test that command is executed without errors
|
||||
@@ -1315,6 +1345,30 @@ func TestGetCallCostLog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxDebitInexistentAcnt(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
cc := &engine.CallCost{}
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
Category: "call",
|
||||
Subject: "INVALID",
|
||||
Account: "INVALID",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
|
||||
}
|
||||
if err := rater.Call("Responder.MaxDebit", cd, cc); err == nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if err := rater.Call("Responder.Debit", cd, cc); err == nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCdrServer(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
@@ -1322,9 +1376,11 @@ func TestCdrServer(t *testing.T) {
|
||||
httpClient := new(http.Client)
|
||||
cdrForm1 := url.Values{"accid": []string{"dsafdsaf"}, "cdrhost": []string{"192.168.1.1"}, "reqtype": []string{"rated"}, "direction": []string{"*out"},
|
||||
"tenant": []string{"cgrates.org"}, "tor": []string{"call"}, "account": []string{"1001"}, "subject": []string{"1001"}, "destination": []string{"1002"},
|
||||
"setup_time": []string{"2013-11-07T08:42:22Z"},
|
||||
"answer_time": []string{"2013-11-07T08:42:26Z"}, "duration": []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}}
|
||||
cdrForm2 := url.Values{"accid": []string{"adsafdsaf"}, "cdrhost": []string{"192.168.1.1"}, "reqtype": []string{"rated"}, "direction": []string{"*out"},
|
||||
"tenant": []string{"cgrates.org"}, "tor": []string{"call"}, "account": []string{"1001"}, "subject": []string{"1001"}, "destination": []string{"1002"},
|
||||
"setup_time": []string{"2013-11-07T08:42:23Z"},
|
||||
"answer_time": []string{"2013-11-07T08:42:26Z"}, "duration": []string{"10"}, "field_extr1": []string{"val_extr1"}, "fieldextr2": []string{"valextr2"}}
|
||||
for _, cdrForm := range []url.Values{cdrForm1, cdrForm2} {
|
||||
cdrForm.Set(utils.CDRSOURCE, engine.TEST_SQL)
|
||||
@@ -1334,23 +1390,28 @@ func TestCdrServer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestExportCdrsToFile(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var reply *utils.ExportedFileCdrs
|
||||
req := utils.AttrExpFileCdrs{}
|
||||
if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err == nil || !strings.HasPrefix(err.Error(), utils.ERR_MANDATORY_IE_MISSING) {
|
||||
t.Error("Failed to detect missing parameter")
|
||||
}
|
||||
req.CdrFormat = utils.CDRE_DRYRUN
|
||||
expectReply := &utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: 2, ExportedCgrIds: []string{utils.FSCgrId("dsafdsaf"), utils.FSCgrId("adsafdsaf")}}
|
||||
//if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err == nil || !strings.HasPrefix(err.Error(), utils.ERR_MANDATORY_IE_MISSING) {
|
||||
// t.Error("Failed to detect missing parameter")
|
||||
//}
|
||||
dryRun := utils.CDRE_DRYRUN
|
||||
req.CdrFormat = &dryRun
|
||||
tm1, _ := utils.ParseTimeDetectLayout("2013-11-07T08:42:22Z")
|
||||
tm2, _ := utils.ParseTimeDetectLayout("2013-11-07T08:42:23Z")
|
||||
expectReply := &utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: 2, ExportedCgrIds: []string{utils.Sha1("dsafdsaf", tm1.String()),
|
||||
utils.Sha1("adsafdsaf", tm2.String())}}
|
||||
if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if !reflect.DeepEqual(reply, expectReply) {
|
||||
t.Errorf("Unexpected reply: %v", reply)
|
||||
}
|
||||
/* Need to implement temporary file writing in order to test removal from db, not possible on DRYRUN
|
||||
Need to implement temporary file writing in order to test removal from db, not possible on DRYRUN
|
||||
req.RemoveFromDb = true
|
||||
if err := rater.Call("ApierV1.ExportCdrsToFile", req, &reply); err != nil {
|
||||
t.Error(err.Error())
|
||||
@@ -1363,7 +1424,232 @@ func TestExportCdrsToFile(t *testing.T) {
|
||||
} else if !reflect.DeepEqual(reply, expectReply) {
|
||||
t.Errorf("Unexpected reply: %v", reply)
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
func TestLocalGetCdrs(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var reply []*utils.StoredCdr
|
||||
req := utils.AttrGetCdrs{}
|
||||
if err := rater.Call("ApierV1.GetCdrs", req, &reply); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if len(reply) != 2 {
|
||||
t.Error("Unexpected number of CDRs returned: ", len(reply))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalProcessCdr(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var reply string
|
||||
cdr := utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf",
|
||||
CdrHost: "192.168.1.1", CdrSource: "test", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
|
||||
Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans",
|
||||
}
|
||||
if err := rater.Call("CDRSV1.ProcessCdr", cdr, &reply); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply received: ", reply)
|
||||
}
|
||||
var cdrs []*utils.StoredCdr
|
||||
req := utils.AttrGetCdrs{}
|
||||
if err := rater.Call("ApierV1.GetCdrs", req, &cdrs); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if len(cdrs) != 3 {
|
||||
t.Error("Unexpected number of CDRs returned: ", len(cdrs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalSetDC(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
dcs1 := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
}
|
||||
attrs := AttrSetDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan", DerivedChargers: dcs1}
|
||||
var reply string
|
||||
if err := rater.Call("ApierV1.SetDerivedChargers", attrs, &reply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply returned", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalGetDC(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
eDcs := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
}
|
||||
var dcs utils.DerivedChargers
|
||||
if err := rater.Call("ApierV1.GetDerivedChargers", attrs, &dcs); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if !reflect.DeepEqual(dcs, eDcs) {
|
||||
t.Errorf("Expecting: %v, received: %v", eDcs, dcs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalRemDC(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
var reply string
|
||||
if err := rater.Call("ApierV1.RemDerivedChargers", attrs, &reply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply returned", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalGetRatingSubjectAliases(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var subjAliases []string
|
||||
if err := rater.Call("ApierV1.GetRatingSubjectAliases", engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}, &subjAliases); err == nil {
|
||||
t.Error("Unexpected nil error received")
|
||||
} else if err.Error() != utils.ERR_NOT_FOUND {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalAddRatingSubjectAliases(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
addRtSubjAliases := &AttrAddRatingSubjectAliases{Tenant: "cgrates.org", Subject: "1001", Aliases: []string{"2001", "2002", "2003"}}
|
||||
var rply string
|
||||
if err := rater.Call("ApierV1.AddRatingSubjectAliases", addRtSubjAliases, &rply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if rply != utils.OK {
|
||||
t.Error("Unexpected reply: ", rply)
|
||||
}
|
||||
var subjAliases []string
|
||||
expectSubjAliases := sort.StringSlice(addRtSubjAliases.Aliases)
|
||||
expectSubjAliases.Sort()
|
||||
if err := rater.Call("ApierV1.GetRatingSubjectAliases", engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}, &subjAliases); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else {
|
||||
subjAliases := sort.StringSlice(subjAliases)
|
||||
subjAliases.Sort()
|
||||
if !reflect.DeepEqual(expectSubjAliases, subjAliases) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectSubjAliases, subjAliases)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalRemRatingSubjectAliases(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
tenantRatingSubj := engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}
|
||||
var rply string
|
||||
if err := rater.Call("ApierV1.RemRatingSubjectAliases", tenantRatingSubj, &rply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if rply != utils.OK {
|
||||
t.Error("Unexpected reply: ", rply)
|
||||
}
|
||||
var subjAliases []string
|
||||
if err := rater.Call("ApierV1.GetRatingSubjectAliases", engine.TenantRatingSubject{Tenant: "cgrates.org", Subject: "1001"}, &subjAliases); err == nil {
|
||||
t.Error("Unexpected nil error received")
|
||||
} else if err.Error() != utils.ERR_NOT_FOUND {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalGetAccountAliases(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
tenantAcnt := engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}
|
||||
var acntAliases []string
|
||||
if err := rater.Call("ApierV1.GetAccountAliases", tenantAcnt, &acntAliases); err == nil {
|
||||
t.Error("Unexpected nil error received")
|
||||
} else if err.Error() != utils.ERR_NOT_FOUND {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalAddAccountAliases(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
addAcntAliases := &AttrAddAccountAliases{Tenant: "cgrates.org", Account: "1001", Aliases: []string{"2001", "2002", "2003"}}
|
||||
var rply string
|
||||
if err := rater.Call("ApierV1.AddAccountAliases", addAcntAliases, &rply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if rply != utils.OK {
|
||||
t.Error("Unexpected reply: ", rply)
|
||||
}
|
||||
var acntAliases []string
|
||||
expectAcntAliases := sort.StringSlice(addAcntAliases.Aliases)
|
||||
expectAcntAliases.Sort()
|
||||
if err := rater.Call("ApierV1.GetAccountAliases", engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}, &acntAliases); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else {
|
||||
acntAliases := sort.StringSlice(acntAliases)
|
||||
acntAliases.Sort()
|
||||
if !reflect.DeepEqual(expectAcntAliases, acntAliases) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectAcntAliases, acntAliases)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalRemAccountAliases(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
tenantAcnt := engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}
|
||||
var rply string
|
||||
if err := rater.Call("ApierV1.RemAccountAliases", tenantAcnt, &rply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if rply != utils.OK {
|
||||
t.Error("Unexpected reply: ", rply)
|
||||
}
|
||||
var acntAliases []string
|
||||
if err := rater.Call("ApierV1.GetAccountAliases", engine.TenantAccount{Tenant: "cgrates.org", Account: "1001"}, &acntAliases); err == nil {
|
||||
t.Error("Unexpected nil error received")
|
||||
} else if err.Error() != utils.ERR_NOT_FOUND {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalGetScheduledActions(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var rply []*ScheduledActions
|
||||
if err := rater.Call("ApierV1.GetScheduledActions", AttrsGetScheduledActions{}, &rply); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalGetDataCost(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
attrs := AttrGetDataCost{Direction: "*out", Category: "data", Tenant: "cgrates.org", Account: "1001", Subject: "1001", StartTime: time.Now(), Usage: 640113}
|
||||
var rply *engine.DataCost
|
||||
if err := rater.Call("ApierV1.GetDataCost", attrs, &rply); err != nil {
|
||||
t.Error("Unexpected nil error received: ", err.Error())
|
||||
} else if rply.Cost != 128.0240 {
|
||||
t.Errorf("Unexpected cost received: %f", rply.Cost)
|
||||
}
|
||||
}
|
||||
|
||||
// Simply kill the engine after we are done with tests within this file
|
||||
|
||||
184
apier/cdre.go
184
apier/cdre.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/cdre"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -34,10 +34,7 @@ import (
|
||||
func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.ExportedFileCdrs) error {
|
||||
var tStart, tEnd time.Time
|
||||
var err error
|
||||
cdrFormat := strings.ToLower(attr.CdrFormat)
|
||||
if !utils.IsSliceMember(utils.CdreCdrFormats, cdrFormat) {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "CdrFormat")
|
||||
}
|
||||
engine.Logger.Debug(fmt.Sprintf("ExportCdrsToFile: %+v", attr))
|
||||
if len(attr.TimeStart) != 0 {
|
||||
if tStart, err = utils.ParseTimeDetectLayout(attr.TimeStart); err != nil {
|
||||
return err
|
||||
@@ -48,94 +45,107 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E
|
||||
return err
|
||||
}
|
||||
}
|
||||
fileName := attr.ExportFileName
|
||||
exportId := attr.ExportId
|
||||
if len(exportId) == 0 {
|
||||
exportId = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
exportTemplate := self.Config.CdreDefaultInstance
|
||||
if attr.ExportTemplate != nil { // XML Template defined, can be field names or xml reference
|
||||
if strings.HasPrefix(*attr.ExportTemplate, utils.XML_PROFILE_PREFIX) {
|
||||
if self.Config.XmlCfgDocument == nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "XmlDocumentNotLoaded")
|
||||
}
|
||||
expTplStr := *attr.ExportTemplate
|
||||
if xmlTemplates := self.Config.XmlCfgDocument.GetCdreCfgs(expTplStr[len(utils.XML_PROFILE_PREFIX):]); xmlTemplates == nil {
|
||||
return fmt.Errorf("%s:ExportTemplate", utils.ERR_NOT_FOUND)
|
||||
} else {
|
||||
exportTemplate = xmlTemplates[expTplStr[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig()
|
||||
}
|
||||
} else {
|
||||
exportTemplate, _ = config.NewDefaultCdreConfig()
|
||||
if contentFlds, err := config.NewCdreCdrFieldsFromIds(exportTemplate.CdrFormat == utils.CDRE_FIXED_WIDTH,
|
||||
strings.Split(*attr.ExportTemplate, string(utils.CSV_SEP))...); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else {
|
||||
exportTemplate.ContentFields = contentFlds
|
||||
}
|
||||
}
|
||||
}
|
||||
roundDecimals := attr.RoundingDecimals
|
||||
if roundDecimals == 0 {
|
||||
roundDecimals = self.Config.RoundingDecimals
|
||||
if exportTemplate == nil {
|
||||
return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING)
|
||||
}
|
||||
cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunId, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction,
|
||||
attr.Tenant, attr.Tor, attr.Account, attr.Subject, attr.DestinationPrefix, tStart, tEnd, attr.SkipErrors, attr.SkipRated)
|
||||
cdrFormat := exportTemplate.CdrFormat
|
||||
if attr.CdrFormat != nil {
|
||||
cdrFormat = strings.ToLower(*attr.CdrFormat)
|
||||
}
|
||||
if !utils.IsSliceMember(utils.CdreCdrFormats, cdrFormat) {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "CdrFormat")
|
||||
}
|
||||
fieldSep := exportTemplate.FieldSeparator
|
||||
if attr.FieldSeparator != nil {
|
||||
fieldSep = *attr.FieldSeparator
|
||||
}
|
||||
exportDir := exportTemplate.ExportDir
|
||||
if attr.ExportDir != nil {
|
||||
exportDir = *attr.ExportDir
|
||||
}
|
||||
exportId := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
if attr.ExportId != nil {
|
||||
exportId = *attr.ExportId
|
||||
}
|
||||
fileName := fmt.Sprintf("cdre_%s.%s", exportId, cdrFormat)
|
||||
if attr.ExportFileName != nil {
|
||||
fileName = *attr.ExportFileName
|
||||
}
|
||||
filePath := path.Join(exportDir, fileName)
|
||||
if cdrFormat == utils.CDRE_DRYRUN {
|
||||
filePath = utils.CDRE_DRYRUN
|
||||
}
|
||||
dataUsageMultiplyFactor := exportTemplate.DataUsageMultiplyFactor
|
||||
if attr.DataUsageMultiplyFactor != nil {
|
||||
dataUsageMultiplyFactor = *attr.DataUsageMultiplyFactor
|
||||
}
|
||||
costMultiplyFactor := exportTemplate.CostMultiplyFactor
|
||||
if attr.CostMultiplyFactor != nil {
|
||||
costMultiplyFactor = *attr.CostMultiplyFactor
|
||||
}
|
||||
costShiftDigits := exportTemplate.CostShiftDigits
|
||||
if attr.CostShiftDigits != nil {
|
||||
costShiftDigits = *attr.CostShiftDigits
|
||||
}
|
||||
roundingDecimals := exportTemplate.CostRoundingDecimals
|
||||
if attr.RoundDecimals != nil {
|
||||
roundingDecimals = *attr.RoundDecimals
|
||||
}
|
||||
maskDestId := exportTemplate.MaskDestId
|
||||
if attr.MaskDestinationId != nil {
|
||||
maskDestId = *attr.MaskDestinationId
|
||||
}
|
||||
maskLen := exportTemplate.MaskLength
|
||||
if attr.MaskLength != nil {
|
||||
maskLen = *attr.MaskLength
|
||||
}
|
||||
cdrs, err := self.CdrDb.GetStoredCdrs(attr.CgrIds, attr.MediationRunIds, attr.TORs, attr.CdrHosts, attr.CdrSources, attr.ReqTypes, attr.Directions,
|
||||
attr.Tenants, attr.Categories, attr.Accounts, attr.Subjects, attr.DestinationPrefixes, attr.RatedAccounts, attr.RatedSubjects, attr.OrderIdStart, attr.OrderIdEnd,
|
||||
tStart, tEnd, attr.SkipErrors, attr.SkipRated, false)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(cdrs) == 0 {
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: ""}
|
||||
return nil
|
||||
}
|
||||
switch cdrFormat {
|
||||
case utils.CDRE_DRYRUN:
|
||||
exportedIds := make([]string, len(cdrs))
|
||||
for idxCdr, cdr := range cdrs {
|
||||
exportedIds[idxCdr] = cdr.CgrId
|
||||
}
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: utils.CDRE_DRYRUN, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds}
|
||||
case utils.CDRE_CSV:
|
||||
if len(fileName) == 0 {
|
||||
fileName = fmt.Sprintf("cdre_%s.csv", exportId)
|
||||
}
|
||||
exportedFields := self.Config.CdreExportedFields
|
||||
if len(attr.ExportTemplate) != 0 {
|
||||
if exportedFields, err = config.ParseRSRFields(attr.ExportTemplate); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
}
|
||||
if len(exportedFields) == 0 {
|
||||
return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING)
|
||||
}
|
||||
filePath := path.Join(self.Config.CdreDir, fileName)
|
||||
fileOut, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileOut.Close()
|
||||
csvWriter := cdre.NewCsvCdrWriter(fileOut, roundDecimals, exportedFields)
|
||||
exportedIds := make([]string, 0)
|
||||
unexportedIds := make(map[string]string)
|
||||
for _, cdr := range cdrs {
|
||||
if err := csvWriter.WriteCdr(cdr); err != nil {
|
||||
unexportedIds[cdr.CgrId] = err.Error()
|
||||
} else {
|
||||
exportedIds = append(exportedIds, cdr.CgrId)
|
||||
}
|
||||
}
|
||||
csvWriter.Close()
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds, UnexportedCgrIds: unexportedIds}
|
||||
case utils.CDRE_FIXED_WIDTH:
|
||||
if len(fileName) == 0 {
|
||||
fileName = fmt.Sprintf("cdre_%s.fwv", exportId)
|
||||
}
|
||||
exportTemplate := self.Config.CdreFWXmlTemplate
|
||||
if len(attr.ExportTemplate) != 0 && self.Config.XmlCfgDocument != nil {
|
||||
if xmlTemplate, err := self.Config.XmlCfgDocument.GetCdreFWCfg(attr.ExportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else if xmlTemplate != nil {
|
||||
exportTemplate = xmlTemplate
|
||||
}
|
||||
}
|
||||
if exportTemplate == nil {
|
||||
return fmt.Errorf("%s:ExportTemplate", utils.ERR_MANDATORY_IE_MISSING)
|
||||
}
|
||||
filePath := path.Join(self.Config.CdreDir, fileName)
|
||||
fileOut, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileOut.Close()
|
||||
fww, _ := cdre.NewFWCdrWriter(self.LogDb, fileOut, exportTemplate, exportId, roundDecimals)
|
||||
exportedIds := make([]string, 0)
|
||||
unexportedIds := make(map[string]string)
|
||||
for _, cdr := range cdrs {
|
||||
if err := fww.WriteCdr(cdr); err != nil {
|
||||
unexportedIds[cdr.CgrId] = err.Error()
|
||||
} else {
|
||||
exportedIds = append(exportedIds, cdr.CgrId)
|
||||
}
|
||||
}
|
||||
fww.Close()
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), ExportedCgrIds: exportedIds, UnexportedCgrIds: unexportedIds}
|
||||
cdrexp, err := cdre.NewCdrExporter(cdrs, self.LogDb, exportTemplate, cdrFormat, fieldSep, exportId,
|
||||
dataUsageMultiplyFactor, costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
if cdrexp.TotalExportedCdrs() == 0 {
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: ""}
|
||||
return nil
|
||||
}
|
||||
if err := cdrexp.WriteToFile(filePath); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.ExportedFileCdrs{ExportedFilePath: filePath, TotalRecords: len(cdrs), TotalCost: cdrexp.TotalCost(), FirstOrderId: cdrexp.FirstOrderId(), LastOrderId: cdrexp.LastOrderId()}
|
||||
if !attr.SuppressCgrIds {
|
||||
reply.ExportedCgrIds = cdrexp.PositiveExports()
|
||||
reply.UnexportedCgrIds = cdrexp.NegativeExports()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AttrGetCallCost struct {
|
||||
@@ -43,3 +44,29 @@ func (apier *ApierV1) GetCallCostLog(attrs AttrGetCallCost, reply *engine.CallCo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieves CDRs based on the filters
|
||||
func (apier *ApierV1) GetCdrs(attrs utils.AttrGetCdrs, reply *[]*utils.CgrCdrOut) error {
|
||||
var tStart, tEnd time.Time
|
||||
var err error
|
||||
if len(attrs.TimeStart) != 0 {
|
||||
if tStart, err = utils.ParseTimeDetectLayout(attrs.TimeStart); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(attrs.TimeEnd) != 0 {
|
||||
if tEnd, err = utils.ParseTimeDetectLayout(attrs.TimeEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cdrs, err := apier.CdrDb.GetStoredCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions,
|
||||
attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects,
|
||||
attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.SkipErrors, attrs.SkipRated, false); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else {
|
||||
for _, cdr := range cdrs {
|
||||
*reply = append(*reply, cdr.AsCgrCdrOut())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
62
apier/cdrstatsv1.go
Normal file
62
apier/cdrstatsv1.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Interact with Stats server
|
||||
type CDRStatsV1 struct {
|
||||
CdrStats *engine.Stats
|
||||
}
|
||||
|
||||
type AttrGetMetrics struct {
|
||||
StatsQueueId string // Id of the stats instance queried
|
||||
}
|
||||
|
||||
func (sts *CDRStatsV1) GetMetrics(attr AttrGetMetrics, reply *map[string]float64) error {
|
||||
if len(attr.StatsQueueId) == 0 {
|
||||
return fmt.Errorf("%s:StatsQueueId", utils.ERR_MANDATORY_IE_MISSING)
|
||||
}
|
||||
return sts.CdrStats.GetValues(attr.StatsQueueId, reply)
|
||||
}
|
||||
|
||||
func (sts *CDRStatsV1) GetQueueIds(empty string, reply *[]string) error {
|
||||
return sts.CdrStats.GetQueueIds(0, reply)
|
||||
}
|
||||
|
||||
func (sts *CDRStatsV1) ReloadQueues(attr utils.AttrCDRStatsReloadQueues, reply *string) error {
|
||||
if err := sts.CdrStats.ReloadQueues(attr.StatsQueueIds, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sts *CDRStatsV1) ResetQueues(attr utils.AttrCDRStatsReloadQueues, reply *string) error {
|
||||
if err := sts.CdrStats.ResetQueues(attr.StatsQueueIds, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
221
apier/cdrstatsv1_local_test.go
Normal file
221
apier/cdrstatsv1_local_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"os/exec"
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var cdrstCfgPath string
|
||||
var cdrstCfg *config.CGRConfig
|
||||
var cdrstRpc *rpc.Client
|
||||
|
||||
func init() {
|
||||
cdrstCfgPath = path.Join(*dataDir, "conf", "samples", "cdrstatsv1_local_test.cfg")
|
||||
cdrstCfg, _ = config.NewCGRConfigFromFile(&cdrstCfgPath)
|
||||
}
|
||||
|
||||
func TestCDRStatsLclInitDataDb(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
ratingDb, err := engine.ConfigureRatingStorage(cdrstCfg.RatingDBType, cdrstCfg.RatingDBHost, cdrstCfg.RatingDBPort, cdrstCfg.RatingDBName,
|
||||
cdrstCfg.RatingDBUser, cdrstCfg.RatingDBPass, cdrstCfg.DBDataEncoding)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot connect to dataDb", err)
|
||||
}
|
||||
accountDb, err := engine.ConfigureAccountingStorage(cdrstCfg.AccountDBType, cdrstCfg.AccountDBHost, cdrstCfg.AccountDBPort, cdrstCfg.AccountDBName,
|
||||
cdrstCfg.AccountDBUser, cdrstCfg.AccountDBPass, cdrstCfg.DBDataEncoding)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot connect to dataDb", err)
|
||||
}
|
||||
for _, db := range []engine.Storage{ratingDb, accountDb} {
|
||||
if err := db.Flush(); err != nil {
|
||||
t.Fatal("Cannot reset dataDb", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDRStatsLclStartEngine(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
enginePath, err := exec.LookPath("cgr-engine")
|
||||
if err != nil {
|
||||
t.Fatal("Cannot find cgr-engine executable")
|
||||
}
|
||||
exec.Command("pkill", "cgr-engine").Run() // Just to make sure another one is not running, bit brutal maybe we can fine tune it
|
||||
engine := exec.Command(enginePath, "-config", cdrstCfgPath)
|
||||
if err := engine.Start(); err != nil {
|
||||
t.Fatal("Cannot start cgr-engine: ", err.Error())
|
||||
}
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time to rater to fire up
|
||||
}
|
||||
|
||||
// Connect rpc client to rater
|
||||
func TestCDRStatsLclRpcConn(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
cdrstRpc, err = jsonrpc.Dial("tcp", cdrstCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
|
||||
if err != nil {
|
||||
t.Fatal("Could not connect to rater: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDRStatsLclGetQueueIds(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var queueIds []string
|
||||
eQueueIds := []string{"*default"}
|
||||
if err := cdrstRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
|
||||
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(eQueueIds, queueIds) {
|
||||
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDRStatsLclLoadTariffPlanFromFolder(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
reply := ""
|
||||
// Simple test that command is executed without errors
|
||||
attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "cdrstats")}
|
||||
if err := cdrstRpc.Call("ApierV1.LoadTariffPlanFromFolder", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.LoadTariffPlanFromFolder: ", err.Error())
|
||||
} else if reply != "OK" {
|
||||
t.Error("Calling ApierV1.LoadTariffPlanFromFolder got reply: ", reply)
|
||||
}
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups
|
||||
}
|
||||
|
||||
func TestCDRStatsLclGetQueueIds2(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var queueIds []string
|
||||
eQueueIds := []string{"*default", "CDRST3", "CDRST4"}
|
||||
if err := cdrstRpc.Call("CDRStatsV1.GetQueueIds", "", &queueIds); err != nil {
|
||||
t.Error("Calling CDRStatsV1.GetQueueIds, got error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(eQueueIds, queueIds) {
|
||||
t.Errorf("Expecting: %v, received: %v", eQueueIds, queueIds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDRStatsLclPostCdrs(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
httpClient := new(http.Client)
|
||||
storedCdrs := []*utils.StoredCdr{
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafa", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(),
|
||||
AnswerTime: time.Now(), MediationRunId: utils.DEFAULT_RUNID,
|
||||
Usage: time.Duration(10) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
|
||||
Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
|
||||
},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafb", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(),
|
||||
AnswerTime: time.Now(), MediationRunId: utils.DEFAULT_RUNID,
|
||||
Usage: time.Duration(5) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
|
||||
},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafc", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(), AnswerTime: time.Now(),
|
||||
MediationRunId: utils.DEFAULT_RUNID,
|
||||
Usage: time.Duration(30) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
|
||||
},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsafd", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), OrderId: 123, TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: "test",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "+4986517174963", SetupTime: time.Now(), AnswerTime: time.Time{},
|
||||
MediationRunId: utils.DEFAULT_RUNID,
|
||||
Usage: time.Duration(0) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dan",
|
||||
},
|
||||
}
|
||||
for _, storedCdr := range storedCdrs {
|
||||
if _, err := httpClient.PostForm(fmt.Sprintf("http://%s/cgr", "127.0.0.1:2080"), storedCdr.AsHttpForm()); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
|
||||
|
||||
}
|
||||
|
||||
func TestCDRStatsLclGetMetrics1(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var rcvMetrics1 map[string]float64
|
||||
expectedMetrics1 := map[string]float64{"ASR": 75, "ACD": 15, "ACC": 15}
|
||||
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "*default"}, &rcvMetrics1); err != nil {
|
||||
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expectedMetrics1, rcvMetrics1) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectedMetrics1, rcvMetrics1)
|
||||
}
|
||||
var rcvMetrics2 map[string]float64
|
||||
expectedMetrics2 := map[string]float64{"ASR": 75, "ACD": 15}
|
||||
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "CDRST4"}, &rcvMetrics2); err != nil {
|
||||
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expectedMetrics2, rcvMetrics2) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectedMetrics2, rcvMetrics2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDRStatsLclResetMetrics(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
var reply string
|
||||
if err := cdrstRpc.Call("CDRStatsV1.ResetQueues", utils.AttrCDRStatsReloadQueues{StatsQueueIds: []string{"CDRST4"}}, &reply); err != nil {
|
||||
t.Error("Calling CDRStatsV1.ResetQueues, got error: ", err.Error())
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply received: ", reply)
|
||||
}
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond)
|
||||
var rcvMetrics1 map[string]float64
|
||||
expectedMetrics1 := map[string]float64{"ASR": 75, "ACD": 15, "ACC": 15}
|
||||
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "*default"}, &rcvMetrics1); err != nil {
|
||||
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expectedMetrics1, rcvMetrics1) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectedMetrics1, rcvMetrics1)
|
||||
}
|
||||
var rcvMetrics2 map[string]float64
|
||||
expectedMetrics2 := map[string]float64{"ASR": 0, "ACD": 0}
|
||||
if err := cdrstRpc.Call("CDRStatsV1.GetMetrics", AttrGetMetrics{StatsQueueId: "CDRST4"}, &rcvMetrics2); err != nil {
|
||||
t.Error("Calling CDRStatsV1.GetMetrics, got error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(expectedMetrics2, rcvMetrics2) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectedMetrics2, rcvMetrics2)
|
||||
}
|
||||
}
|
||||
41
apier/cdrsv1.go
Normal file
41
apier/cdrsv1.go
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Receive CDRs via RPC methods
|
||||
type CDRSV1 struct {
|
||||
CdrSrv *engine.CDRS
|
||||
}
|
||||
|
||||
func (cdrsrv *CDRSV1) ProcessCdr(cdr *utils.StoredCdr, reply *string) error {
|
||||
if cdrsrv.CdrSrv == nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, "CDRS_NOT_RUNNING")
|
||||
}
|
||||
if err := cdrsrv.CdrSrv.ProcessCdr(cdr); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
59
apier/costs.go
Normal file
59
apier/costs.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AttrGetDataCost struct {
|
||||
Direction string
|
||||
Category string
|
||||
Tenant, Account, Subject string
|
||||
StartTime time.Time
|
||||
Usage int64 // the call duration so far (till TimeEnd)
|
||||
}
|
||||
|
||||
func (apier *ApierV1) GetDataCost(attrs AttrGetDataCost, reply *engine.DataCost) error {
|
||||
usageAsDuration := time.Duration(attrs.Usage) * time.Second // Convert to seconds to match the loaded rates
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: attrs.Direction,
|
||||
Category: attrs.Category,
|
||||
Tenant: attrs.Tenant,
|
||||
Account: attrs.Account,
|
||||
Subject: attrs.Subject,
|
||||
TimeStart: attrs.StartTime,
|
||||
TimeEnd: attrs.StartTime.Add(usageAsDuration),
|
||||
DurationIndex: usageAsDuration,
|
||||
TOR: utils.DATA,
|
||||
}
|
||||
var cc engine.CallCost
|
||||
if err := apier.Responder.GetCost(cd, &cc); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
if dc, err := cc.ToDataCost(); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else if dc != nil {
|
||||
*reply = *dc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
82
apier/derivedcharging.go
Normal file
82
apier/derivedcharging.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Get DerivedChargers applying to our call, appends general configured to account specific ones if that is configured
|
||||
func (self *ApierV1) GetDerivedChargers(attrs utils.AttrDerivedChargers, reply *utils.DerivedChargers) (err error) {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Direction", "Account", "Subject"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if hDc, err := engine.HandleGetDerivedChargers(self.AccountDb, self.Config, attrs); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else if hDc != nil {
|
||||
*reply = hDc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrSetDerivedChargers struct {
|
||||
Direction, Tenant, Category, Account, Subject string
|
||||
DerivedChargers utils.DerivedChargers
|
||||
}
|
||||
|
||||
func (self *ApierV1) SetDerivedChargers(attrs AttrSetDerivedChargers, reply *string) (err error) {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Tenant", "Category", "Direction", "Account", "Subject", "DerivedChargers"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
for _, dc := range attrs.DerivedChargers {
|
||||
if _, err = utils.ParseRSRFields(dc.RunFilters, utils.INFIELD_SEP); err != nil { // Make sure rules are OK before loading in db
|
||||
return fmt.Errorf("%s:%s", utils.ERR_PARSER_ERROR, err.Error())
|
||||
}
|
||||
}
|
||||
dcKey := utils.DerivedChargersKey(attrs.Direction, attrs.Tenant, attrs.Category, attrs.Account, attrs.Subject)
|
||||
if err := self.AccountDb.SetDerivedChargers(dcKey, attrs.DerivedChargers); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
if err := self.AccountDb.CacheAccounting([]string{}, []string{}, []string{}, []string{engine.DERIVEDCHARGERS_PREFIX + dcKey}); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttrRemDerivedChargers struct {
|
||||
Direction, Tenant, Category, Account, Subject string
|
||||
}
|
||||
|
||||
func (self *ApierV1) RemDerivedChargers(attrs AttrRemDerivedChargers, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"Direction", "Tenant", "Category", "Account", "Subject"}); len(missing) != 0 { //Params missing
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if err := self.AccountDb.SetDerivedChargers(utils.DerivedChargersKey(attrs.Direction, attrs.Tenant, attrs.Category, attrs.Account, attrs.Subject), nil); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else {
|
||||
*reply = "OK"
|
||||
}
|
||||
if err := self.AccountDb.CacheAccounting([]string{}, []string{}, []string{}, nil); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
101
apier/derivedcharging_test.go
Normal file
101
apier/derivedcharging_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can Storagetribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITH*out ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var apierDcT *ApierV1
|
||||
|
||||
func init() {
|
||||
dataStorage, _ := engine.NewMapStorage()
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
apierDcT = &ApierV1{AccountDb: engine.AccountingStorage(dataStorage), Config: cfg}
|
||||
}
|
||||
|
||||
func TestGetEmptyDC(t *testing.T) {
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
var dcs utils.DerivedChargers
|
||||
if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if !reflect.DeepEqual(dcs, apierDcT.Config.DerivedChargers) {
|
||||
t.Error("Returned DerivedChargers not matching the configured ones")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDC(t *testing.T) {
|
||||
dcs1 := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
}
|
||||
attrs := AttrSetDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan", DerivedChargers: dcs1}
|
||||
var reply string
|
||||
if err := apierDcT.SetDerivedChargers(attrs, &reply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply returned", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDC(t *testing.T) {
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
eDcs := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "extra1", ReqTypeField: "^prepaid", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "rif", SubjectField: "rif", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
&utils.DerivedCharger{RunId: "extra2", ReqTypeField: "*default", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "ivo", SubjectField: "ivo", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
}
|
||||
var dcs utils.DerivedChargers
|
||||
if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if !reflect.DeepEqual(dcs, eDcs) {
|
||||
t.Errorf("Expecting: %v, received: %v", eDcs, dcs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemDC(t *testing.T) {
|
||||
attrs := AttrRemDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "dan", Subject: "dan"}
|
||||
var reply string
|
||||
if err := apierDcT.RemDerivedChargers(attrs, &reply); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if reply != utils.OK {
|
||||
t.Error("Unexpected reply returned", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEmptyDC2(t *testing.T) {
|
||||
attrs := utils.AttrDerivedChargers{Tenant: "cgrates.org", Category: "call", Direction: "*out", Account: "dan", Subject: "dan"}
|
||||
var dcs utils.DerivedChargers
|
||||
if err := apierDcT.GetDerivedChargers(attrs, &dcs); err != nil {
|
||||
t.Error("Unexpected error", err.Error())
|
||||
} else if !reflect.DeepEqual(dcs, apierDcT.Config.DerivedChargers) {
|
||||
for _, dc := range dcs {
|
||||
fmt.Printf("Got dc: %v\n", dc)
|
||||
}
|
||||
t.Error("Returned DerivedChargers not matching the configured ones")
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,18 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package mediator
|
||||
package apier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
type MediatorV1 struct {
|
||||
Medi *Mediator
|
||||
Medi *engine.Mediator
|
||||
}
|
||||
|
||||
// Remotely start mediation with specific runid, runs asynchronously, it's status will be displayed in syslog
|
||||
@@ -45,7 +47,11 @@ func (self *MediatorV1) RateCdrs(attrs utils.AttrRateCdrs, reply *string) error
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := self.Medi.RateCdrs(tStart, tEnd, attrs.RerateErrors, attrs.RerateRated); err != nil {
|
||||
//RateCdrs(cgrIds, runIds, tors, cdrHosts, cdrSources, reqTypes, directions, tenants, categories, accounts, subjects, destPrefixes []string,
|
||||
//orderIdStart, orderIdEnd int64, timeStart, timeEnd time.Time, rerateErrors, rerateRated bool)
|
||||
if err := self.Medi.RateCdrs(attrs.CgrIds, attrs.MediationRunIds, attrs.TORs, attrs.CdrHosts, attrs.CdrSources, attrs.ReqTypes, attrs.Directions,
|
||||
attrs.Tenants, attrs.Categories, attrs.Accounts, attrs.Subjects, attrs.DestinationPrefixes, attrs.RatedAccounts, attrs.RatedSubjects,
|
||||
attrs.OrderIdStart, attrs.OrderIdEnd, tStart, tEnd, attrs.RerateErrors, attrs.RerateRated, attrs.SendToStats); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
*reply = utils.OK
|
||||
151
apier/scheduler.go
Normal file
151
apier/scheduler.go
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package apier
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
[
|
||||
{
|
||||
u'ActionsId': u'BONUS_1',
|
||||
u'Uuid': u'5b5ba53b40b1d44380cce52379ec5c0d',
|
||||
u'Weight': 10,
|
||||
u'Timing': {
|
||||
u'Timing': {
|
||||
u'MonthDays': [
|
||||
|
||||
],
|
||||
u'Months': [
|
||||
|
||||
],
|
||||
u'WeekDays': [
|
||||
|
||||
],
|
||||
u'Years': [
|
||||
2013
|
||||
],
|
||||
u'StartTime': u'11: 00: 00',
|
||||
u'EndTime': u''
|
||||
},
|
||||
u'Rating': None,
|
||||
u'Weight': 0
|
||||
},
|
||||
u'AccountIds': [
|
||||
u'*out: cgrates.org: 1001',
|
||||
u'*out: cgrates.org: 1002',
|
||||
u'*out: cgrates.org: 1003',
|
||||
u'*out: cgrates.org: 1004',
|
||||
u'*out: cgrates.org: 1005'
|
||||
],
|
||||
u'Id': u'PREPAID_10'
|
||||
},
|
||||
{
|
||||
u'ActionsId': u'PREPAID_10',
|
||||
u'Uuid': u'b16ab12740e2e6c380ff7660e8b55528',
|
||||
u'Weight': 10,
|
||||
u'Timing': {
|
||||
u'Timing': {
|
||||
u'MonthDays': [
|
||||
|
||||
],
|
||||
u'Months': [
|
||||
|
||||
],
|
||||
u'WeekDays': [
|
||||
|
||||
],
|
||||
u'Years': [
|
||||
2013
|
||||
],
|
||||
u'StartTime': u'11: 00: 00',
|
||||
u'EndTime': u''
|
||||
},
|
||||
u'Rating': None,
|
||||
u'Weight': 0
|
||||
},
|
||||
u'AccountIds': [
|
||||
u'*out: cgrates.org: 1001',
|
||||
u'*out: cgrates.org: 1002',
|
||||
u'*out: cgrates.org: 1003',
|
||||
u'*out: cgrates.org: 1004',
|
||||
u'*out: cgrates.org: 1005'
|
||||
],
|
||||
u'Id': u'PREPAID_10'
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
type AttrsGetScheduledActions struct {
|
||||
Direction, Tenant, Account string
|
||||
TimeStart, TimeEnd time.Time // Filter based on next runTime
|
||||
}
|
||||
|
||||
type ScheduledActions struct {
|
||||
NextRunTime time.Time
|
||||
Accounts []*utils.DirectionTenantAccount
|
||||
ActionsId, ActionPlanId, ActionPlanUuid string
|
||||
}
|
||||
|
||||
func (self *ApierV1) GetScheduledActions(attrs AttrsGetScheduledActions, reply *[]*ScheduledActions) error {
|
||||
schedActions := make([]*ScheduledActions, 0)
|
||||
if self.Sched == nil {
|
||||
return errors.New("SCHEDULER_NOT_ENABLED")
|
||||
}
|
||||
for _, qActions := range self.Sched.GetQueue() {
|
||||
sas := &ScheduledActions{ActionsId: qActions.ActionsId, ActionPlanId: qActions.Id, ActionPlanUuid: qActions.Uuid}
|
||||
sas.NextRunTime = qActions.GetNextStartTime(time.Now())
|
||||
if !attrs.TimeStart.IsZero() && sas.NextRunTime.Before(attrs.TimeStart) {
|
||||
continue // Filter here only requests in the filtered interval
|
||||
}
|
||||
if !attrs.TimeEnd.IsZero() && (sas.NextRunTime.After(attrs.TimeEnd) || sas.NextRunTime.Equal(attrs.TimeEnd)) {
|
||||
continue
|
||||
}
|
||||
acntFiltersMatch := false
|
||||
for _, acntKey := range qActions.AccountIds {
|
||||
directionMatched := len(attrs.Direction) == 0
|
||||
tenantMatched := len(attrs.Tenant) == 0
|
||||
accountMatched := len(attrs.Account) == 0
|
||||
dta, _ := utils.NewDTAFromAccountKey(acntKey)
|
||||
sas.Accounts = append(sas.Accounts, dta)
|
||||
// One member matching
|
||||
if !directionMatched && attrs.Direction == dta.Direction {
|
||||
directionMatched = true
|
||||
}
|
||||
if !tenantMatched && attrs.Tenant == dta.Tenant {
|
||||
tenantMatched = true
|
||||
}
|
||||
if !accountMatched && attrs.Account == dta.Account {
|
||||
accountMatched = true
|
||||
}
|
||||
if directionMatched && tenantMatched && accountMatched {
|
||||
acntFiltersMatch = true
|
||||
}
|
||||
}
|
||||
if !acntFiltersMatch {
|
||||
continue
|
||||
}
|
||||
schedActions = append(schedActions, sas)
|
||||
}
|
||||
*reply = schedActions
|
||||
return nil
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (self *ApierV1) GetTPDestination(attrs AttrGetTPDestination, reply *utils.T
|
||||
} else if len(dsts) == 0 {
|
||||
return errors.New(utils.ERR_NOT_FOUND)
|
||||
} else {
|
||||
*reply = utils.TPDestination{attrs.TPid, dsts[0].Id, dsts[0].Prefixes}
|
||||
*reply = utils.TPDestination{attrs.TPid, dsts[attrs.DestinationId].Id, dsts[attrs.DestinationId].Prefixes}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
|
||||
// Creates a new RatingProfile within a tariff plan
|
||||
func (self *ApierV1) SetTPRatingProfile(attrs utils.TPRatingProfile, reply *string) error {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "TOR", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 {
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Category", "Direction", "Subject", "RatingPlanActivations"}); len(missing) != 0 {
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if err := self.StorDb.SetTPRatingProfiles(attrs.TPid, map[string]*utils.TPRatingProfile{attrs.KeyId(): &attrs}); err != nil {
|
||||
@@ -78,7 +78,7 @@ func (self *ApierV1) GetTPRatingProfileLoadIds(attrs utils.AttrTPRatingProfileId
|
||||
}
|
||||
if ids, err := self.StorDb.GetTPTableIds(attrs.TPid, utils.TBL_TP_RATE_PROFILES, "loadid", map[string]string{
|
||||
"tenant": attrs.Tenant,
|
||||
"tor": attrs.TOR,
|
||||
"tor": attrs.Category,
|
||||
"direction": attrs.Direction,
|
||||
"subject": attrs.Subject,
|
||||
}); err != nil {
|
||||
@@ -96,7 +96,7 @@ func (self *ApierV1) RemTPRatingProfile(attrs utils.TPRatingProfile, reply *stri
|
||||
if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "TOR", "Direction", "Subject"}); len(missing) != 0 { //Params missing
|
||||
return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing)
|
||||
}
|
||||
if err := self.StorDb.RemTPData(utils.TBL_TP_RATE_PROFILES, attrs.TPid, attrs.LoadId, attrs.Tenant, attrs.TOR, attrs.Direction, attrs.Subject); err != nil {
|
||||
if err := self.StorDb.RemTPData(utils.TBL_TP_RATE_PROFILES, attrs.TPid, attrs.LoadId, attrs.Tenant, attrs.Category, attrs.Direction, attrs.Subject); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
} else {
|
||||
*reply = "OK"
|
||||
|
||||
@@ -38,7 +38,7 @@ var fscsvCfg *config.CGRConfig
|
||||
|
||||
func init() {
|
||||
fscsvCfgPath = path.Join(*dataDir, "tutorials", "fs_csv", "cgrates", "etc", "cgrates", "cgrates.cfg")
|
||||
fscsvCfg, _ = config.NewCGRConfig(&fscsvCfgPath)
|
||||
fscsvCfg, _ = config.NewCGRConfigFromFile(&fscsvCfgPath)
|
||||
}
|
||||
|
||||
// Remove here so they can be properly created by init script
|
||||
@@ -46,7 +46,7 @@ func TestFsCsvRemoveDirs(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
for _, pathDir := range []string{cfg.CdreDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
|
||||
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.HistoryDir} {
|
||||
if err := os.RemoveAll(pathDir); err != nil {
|
||||
t.Fatal("Error removing folder: ", pathDir, err)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func TestFsCsvCreateTables(t *testing.T) {
|
||||
} else {
|
||||
mysql = d.(*engine.MySQLStorage)
|
||||
}
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
|
||||
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
|
||||
t.Fatal("Error on mysql creation: ", err.Error())
|
||||
return // No point in going further
|
||||
@@ -182,11 +182,11 @@ func TestFsCsvGetAccount(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var reply *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 { // We expect 11.5 since we have added in the previous test 1.5
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 { // We expect 11.5 since we have added in the previous test 1.5
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,15 +197,15 @@ func TestFsCsvCall1(t *testing.T) {
|
||||
tStart := time.Date(2014, 01, 15, 6, 0, 0, 0, time.UTC)
|
||||
tEnd := time.Date(2014, 01, 15, 6, 0, 35, 0, time.UTC)
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
TOR: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
CallDuration: 35,
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
DurationIndex: 35,
|
||||
}
|
||||
var cc engine.CallCost
|
||||
// Make sure the cost is what we expect it is
|
||||
@@ -222,26 +222,26 @@ func TestFsCsvCall1(t *testing.T) {
|
||||
}
|
||||
// Make sure the account was debited correctly for the first loop index (ConnectFee included)
|
||||
var reply *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 9.4 { // We expect 11.5 since we have added in the previous test 1.5
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 9.4, received: %f", reply.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
} else if reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 9.4 { // We expect 11.5 since we have added in the previous test 1.5
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 9.4, received: %f", reply.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
} else if len(reply.UnitCounters) != 1 ||
|
||||
utils.Round(reply.UnitCounters[0].Balances[0].Value, 2, utils.ROUNDING_MIDDLE) != 0.6 { // Make sure we correctly count usage
|
||||
t.Errorf("Received unexpected UnitCounters: %v", reply.UnitCounters)
|
||||
}
|
||||
cd = engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
TOR: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
CallDuration: 35,
|
||||
LoopIndex: 1, // Should not charge ConnectFee
|
||||
Direction: "*out",
|
||||
Category: "call",
|
||||
Tenant: "cgrates.org",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: tStart,
|
||||
TimeEnd: tEnd,
|
||||
DurationIndex: 35,
|
||||
LoopIndex: 1, // Should not charge ConnectFee
|
||||
}
|
||||
// Make sure debit charges what cost returned
|
||||
if err := rater.Call("Responder.MaxDebit", cd, &cc); err != nil {
|
||||
@@ -253,8 +253,8 @@ func TestFsCsvCall1(t *testing.T) {
|
||||
var reply2 *engine.Account
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &reply2); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else if utils.Round(reply2.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue(), 2, utils.ROUNDING_MIDDLE) != 9.20 {
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 9.2, received: %f", reply2.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
} else if utils.Round(reply2.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue(), 2, utils.ROUNDING_MIDDLE) != 9.20 {
|
||||
t.Errorf("Calling ApierV1.GetAccount expected: 9.2, received: %f", reply2.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
} else if len(reply2.UnitCounters) != 1 ||
|
||||
utils.Round(reply2.UnitCounters[0].Balances[0].Value, 2, utils.ROUNDING_MIDDLE) != 0.8 { // Make sure we correctly count usage
|
||||
t.Errorf("Received unexpected UnitCounters: %v", reply2.UnitCounters)
|
||||
|
||||
@@ -41,7 +41,7 @@ var waitFs = flag.Int("wait_fs", 500, "Number of miliseconds to wait for FreeSWI
|
||||
|
||||
func init() {
|
||||
fsjsonCfgPath = path.Join(*dataDir, "tutorials", "fs_json", "cgrates", "etc", "cgrates", "cgrates.cfg")
|
||||
fsjsonCfg, _ = config.NewCGRConfig(&fsjsonCfgPath)
|
||||
fsjsonCfg, _ = config.NewCGRConfigFromFile(&fsjsonCfgPath)
|
||||
}
|
||||
|
||||
// Remove here so they can be properly created by init script
|
||||
@@ -49,7 +49,7 @@ func TestFsJsonRemoveDirs(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
for _, pathDir := range []string{fsjsonCfg.CdreDir, fsjsonCfg.HistoryDir} {
|
||||
for _, pathDir := range []string{cfg.CdreDefaultInstance.ExportDir, fsjsonCfg.HistoryDir} {
|
||||
if err := os.RemoveAll(pathDir); err != nil {
|
||||
t.Fatal("Error removing folder: ", pathDir, err)
|
||||
}
|
||||
@@ -70,11 +70,9 @@ func TestFsJsonCreateTables(t *testing.T) {
|
||||
} else {
|
||||
mysql = d.(*engine.MySQLStorage)
|
||||
}
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL} {
|
||||
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
|
||||
t.Fatal("Error on mysql creation: ", err.Error())
|
||||
return // No point in going further
|
||||
}
|
||||
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, engine.CREATE_CDRS_TABLES_SQL)); err != nil {
|
||||
t.Fatal("Error on mysql creation: ", err.Error())
|
||||
return // No point in going further
|
||||
}
|
||||
for _, tbl := range []string{utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA} {
|
||||
if _, err := mysql.Db.Query(fmt.Sprintf("SELECT 1 from %s", tbl)); err != nil {
|
||||
@@ -171,7 +169,7 @@ func TestFsJsonLoadTariffPlans(t *testing.T) {
|
||||
}
|
||||
time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups
|
||||
var rcvStats *utils.CacheStats
|
||||
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 2, RatingProfiles: 2, Actions: 5, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1}
|
||||
expectedStats := &utils.CacheStats{Destinations: 3, RatingPlans: 3, RatingProfiles: 3, Actions: 5, SharedGroups: 1, RatingAliases: 1, AccountAliases: 1, DerivedChargers: 1}
|
||||
var args utils.AttrCacheStats
|
||||
if err := rater.Call("ApierV1.GetCacheStats", args, &rcvStats); err != nil {
|
||||
t.Error("Got error on ApierV1.GetCacheStats: ", err.Error())
|
||||
@@ -185,17 +183,17 @@ func TestFsJsonGetAccount1001(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var acnt *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
}
|
||||
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 2 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
|
||||
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 2 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
|
||||
}
|
||||
blncLst := acnt.BalanceMap[attrs.BalanceType+attrs.Direction]
|
||||
blncLst := acnt.BalanceMap[engine.CREDIT+attrs.Direction]
|
||||
for _, blnc := range blncLst {
|
||||
if len(blnc.SharedGroup) == 0 && blnc.Value != 5 {
|
||||
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
|
||||
@@ -210,17 +208,17 @@ func TestFsJsonGetAccount1002(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var acnt *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1002", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1002", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
}
|
||||
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
|
||||
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
|
||||
}
|
||||
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
|
||||
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
|
||||
if blnc.Value != 10 {
|
||||
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
|
||||
}
|
||||
@@ -231,17 +229,17 @@ func TestFsJsonGetAccount1003(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var acnt *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1003", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1003", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
}
|
||||
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
|
||||
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
|
||||
}
|
||||
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
|
||||
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
|
||||
if blnc.Value != 10 {
|
||||
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
|
||||
}
|
||||
@@ -252,17 +250,17 @@ func TestFsJsonGetAccount1004(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var acnt *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1004", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1004", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
}
|
||||
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 10.0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 10.0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
|
||||
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
|
||||
}
|
||||
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
|
||||
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
|
||||
if blnc.Value != 10 {
|
||||
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
|
||||
}
|
||||
@@ -273,7 +271,7 @@ func TestFsJsonGetAccount1006(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var acnt *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1006", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1006", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err == nil {
|
||||
t.Error("Got no error when querying unexisting balance")
|
||||
}
|
||||
@@ -284,17 +282,17 @@ func TestFsJsonGetAccount1007(t *testing.T) {
|
||||
return
|
||||
}
|
||||
var acnt *engine.Account
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1007", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1007", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
}
|
||||
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
|
||||
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
|
||||
}
|
||||
blncLst := acnt.BalanceMap[attrs.BalanceType+attrs.Direction]
|
||||
blncLst := acnt.BalanceMap[engine.CREDIT+attrs.Direction]
|
||||
for _, blnc := range blncLst {
|
||||
if len(blnc.SharedGroup) == 0 && blnc.Value != 0 { // General balance
|
||||
t.Errorf("Unexpected value for general balance: %f", blnc.Value)
|
||||
@@ -311,12 +309,12 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
TOR: "call",
|
||||
Category: "call",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
var remainingDurationFloat float64
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
@@ -330,12 +328,12 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
cd = engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
TOR: "call",
|
||||
Category: "call",
|
||||
Subject: "1002",
|
||||
Account: "1002",
|
||||
Destination: "1001",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
t.Error(err)
|
||||
@@ -348,12 +346,12 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
cd = engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
TOR: "call",
|
||||
Category: "call",
|
||||
Subject: "1006",
|
||||
Account: "1006",
|
||||
Destination: "1001",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
t.Error(err)
|
||||
@@ -367,12 +365,12 @@ func TestMaxCallDuration(t *testing.T) {
|
||||
cd = engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
TOR: "call",
|
||||
Category: "call",
|
||||
Subject: "1007",
|
||||
Account: "1007",
|
||||
Destination: "1001",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(fsjsonCfg.SMMaxCallDuration),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(fsjsonCfg.SMMaxCallDuration),
|
||||
}
|
||||
if err := rater.Call("Responder.GetMaxSessionTime", cd, &remainingDurationFloat); err != nil {
|
||||
t.Error(err)
|
||||
@@ -393,19 +391,19 @@ func TestMaxDebit1001(t *testing.T) {
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
TOR: "call",
|
||||
Category: "call",
|
||||
Subject: "1001",
|
||||
Account: "1001",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(time.Duration(10) * time.Second),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
|
||||
}
|
||||
if err := rater.Call("Responder.MaxDebit", cd, cc); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cc.GetDuration() > time.Duration(1)*time.Minute {
|
||||
t.Errorf("Unexpected call duration received: %v", cc.GetDuration())
|
||||
}
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else {
|
||||
@@ -432,12 +430,12 @@ func TestMaxDebit1007(t *testing.T) {
|
||||
cd := engine.CallDescriptor{
|
||||
Direction: "*out",
|
||||
Tenant: "cgrates.org",
|
||||
TOR: "call",
|
||||
Category: "call",
|
||||
Subject: "1007",
|
||||
Account: "1007",
|
||||
Destination: "1002",
|
||||
TimeStart: time.Now(),
|
||||
TimeEnd: time.Now().Add(time.Duration(10) * time.Second),
|
||||
TimeStart: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, 3, 27, 10, 42, 26, 0, time.UTC).Add(time.Duration(10) * time.Second),
|
||||
}
|
||||
if err := rater.Call("Responder.MaxDebit", cd, cc); err != nil {
|
||||
t.Error(err.Error())
|
||||
@@ -445,7 +443,7 @@ func TestMaxDebit1007(t *testing.T) {
|
||||
t.Errorf("Unexpected call duration received: %v", cc.GetDuration())
|
||||
}
|
||||
// Debit out of shared balance should reflect in the 1001 instead of 1007
|
||||
attrs := &AttrGetAccount{Tenant: "cgrates.org", Account: "1001", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
} else {
|
||||
@@ -462,17 +460,17 @@ func TestMaxDebit1007(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Make sure 1007 remains the same
|
||||
attrs = &AttrGetAccount{Tenant: "cgrates.org", Account: "1007", BalanceType: "*monetary", Direction: "*out"}
|
||||
attrs = &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1007", Direction: "*out"}
|
||||
if err := rater.Call("ApierV1.GetAccount", attrs, &acnt); err != nil {
|
||||
t.Error("Got error on ApierV1.GetAccount: ", err.Error())
|
||||
}
|
||||
if acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue() != 0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[attrs.BalanceType+attrs.Direction].GetTotalValue())
|
||||
if acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue() != 0 {
|
||||
t.Errorf("Calling ApierV1.GetBalance expected: 0, received: %f", acnt.BalanceMap[engine.CREDIT+attrs.Direction].GetTotalValue())
|
||||
}
|
||||
if len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[attrs.BalanceType+attrs.Direction]))
|
||||
if len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]) != 1 {
|
||||
t.Errorf("Unexpected number of balances found: %d", len(acnt.BalanceMap[engine.CREDIT+attrs.Direction]))
|
||||
}
|
||||
blnc := acnt.BalanceMap[attrs.BalanceType+attrs.Direction][0]
|
||||
blnc := acnt.BalanceMap[engine.CREDIT+attrs.Direction][0]
|
||||
if len(blnc.SharedGroup) == 0 { // General balance
|
||||
t.Errorf("Unexpected general balance: %f", blnc.Value)
|
||||
} else if blnc.SharedGroup == "SHARED_A" && blnc.Value != 0 {
|
||||
@@ -480,6 +478,30 @@ func TestMaxDebit1007(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivedChargers1001(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
attrs := &utils.AttrDerivedChargers{Direction: "*out", Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001"}
|
||||
expectedDCs := utils.DerivedChargers([]*utils.DerivedCharger{
|
||||
&utils.DerivedCharger{RunId: "fs_json_run", ReqTypeField: "^rated", DirectionField: "*default", TenantField: "*default", CategoryField: "*default",
|
||||
AccountField: "*default", SubjectField: "^1002", DestinationField: "*default", SetupTimeField: "*default", AnswerTimeField: "*default", UsageField: "*default"},
|
||||
})
|
||||
var rcvRspDCs utils.DerivedChargers
|
||||
if err := rater.Call("Responder.GetDerivedChargers", attrs, &rcvRspDCs); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if !reflect.DeepEqual(expectedDCs, rcvRspDCs) {
|
||||
t.Errorf("Expected: %v: received: %v", expectedDCs, rcvRspDCs)
|
||||
}
|
||||
// Make sure that over Apier we get the same result as over Responder
|
||||
var rcvApierDCs utils.DerivedChargers
|
||||
if err := rater.Call("ApierV1.GetDerivedChargers", attrs, &rcvApierDCs); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if !reflect.DeepEqual(rcvRspDCs, rcvApierDCs) {
|
||||
t.Errorf("Expected: %v: received: %v", rcvRspDCs, rcvApierDCs)
|
||||
}
|
||||
}
|
||||
|
||||
// Simply kill the engine after we are done with tests within this file
|
||||
func TestFsJsonStopEngine(t *testing.T) {
|
||||
if !*testLocal {
|
||||
|
||||
@@ -31,11 +31,17 @@ type timestampedValue struct {
|
||||
value interface{}
|
||||
}
|
||||
|
||||
const (
|
||||
PREFIX_LEN = 4
|
||||
)
|
||||
|
||||
var (
|
||||
xcache = make(map[string]expiringCacheEntry)
|
||||
xMux sync.RWMutex
|
||||
cache = make(map[string]timestampedValue)
|
||||
mux sync.RWMutex
|
||||
xcache = make(map[string]expiringCacheEntry)
|
||||
xMux sync.RWMutex
|
||||
cache = make(map[string]timestampedValue)
|
||||
mux sync.RWMutex
|
||||
cMux sync.Mutex
|
||||
counters = make(map[string]int64)
|
||||
)
|
||||
|
||||
// The main function to cache with expiration
|
||||
@@ -45,6 +51,10 @@ func (xe *XEntry) XCache(key string, expire time.Duration, value expiringCacheEn
|
||||
xe.expireDuration = expire
|
||||
xe.timestamp = time.Now()
|
||||
xMux.Lock()
|
||||
if _, ok := xcache[key]; !ok {
|
||||
// only count if the key is not already there
|
||||
count(key)
|
||||
}
|
||||
xcache[key] = value
|
||||
xMux.Unlock()
|
||||
go xe.expire()
|
||||
@@ -60,7 +70,10 @@ func (xe *XEntry) expire() {
|
||||
<-xe.t.C
|
||||
if !xe.keepAlive {
|
||||
xMux.Lock()
|
||||
delete(xcache, xe.key)
|
||||
if _, ok := xcache[xe.key]; ok {
|
||||
delete(xcache, xe.key)
|
||||
descount(xe.key)
|
||||
}
|
||||
xMux.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -98,6 +111,10 @@ func GetXCached(key string) (ece expiringCacheEntry, err error) {
|
||||
func Cache(key string, value interface{}) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
if _, ok := cache[key]; !ok {
|
||||
// only count if the key is not already there
|
||||
count(key)
|
||||
}
|
||||
cache[key] = timestampedValue{time.Now(), value}
|
||||
}
|
||||
|
||||
@@ -117,10 +134,6 @@ func GetKeyAge(key string) (time.Duration, error) {
|
||||
if r, ok := cache[key]; ok {
|
||||
return time.Since(r.timestamp), nil
|
||||
}
|
||||
return 0, errors.New("not found")
|
||||
}
|
||||
|
||||
func GetXKeyAge(key string) (time.Duration, error) {
|
||||
xMux.RLock()
|
||||
defer xMux.RUnlock()
|
||||
if r, ok := xcache[key]; ok {
|
||||
@@ -131,33 +144,34 @@ func GetXKeyAge(key string) (time.Duration, error) {
|
||||
|
||||
func RemKey(key string) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
delete(cache, key)
|
||||
}
|
||||
|
||||
func RemPrefixKey(prefix string) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
for key, _ := range cache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
delete(cache, key)
|
||||
}
|
||||
if _, ok := cache[key]; ok {
|
||||
delete(cache, key)
|
||||
descount(key)
|
||||
}
|
||||
}
|
||||
|
||||
func XRemKey(key string) {
|
||||
mux.Unlock()
|
||||
xMux.Lock()
|
||||
defer xMux.Unlock()
|
||||
if r, ok := xcache[key]; ok {
|
||||
if r.timer() != nil {
|
||||
r.timer().Stop()
|
||||
}
|
||||
}
|
||||
delete(xcache, key)
|
||||
if _, ok := xcache[key]; ok {
|
||||
delete(xcache, key)
|
||||
descount(key)
|
||||
}
|
||||
xMux.Unlock()
|
||||
}
|
||||
func XRemPrefixKey(prefix string) {
|
||||
|
||||
func RemPrefixKey(prefix string) {
|
||||
mux.Lock()
|
||||
for key, _ := range cache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
delete(cache, key)
|
||||
descount(key)
|
||||
}
|
||||
}
|
||||
mux.Unlock()
|
||||
xMux.Lock()
|
||||
defer xMux.Unlock()
|
||||
for key, _ := range xcache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
if r, ok := xcache[key]; ok {
|
||||
@@ -166,42 +180,102 @@ func XRemPrefixKey(prefix string) {
|
||||
}
|
||||
}
|
||||
delete(xcache, key)
|
||||
descount(key)
|
||||
}
|
||||
}
|
||||
xMux.Unlock()
|
||||
}
|
||||
|
||||
// Delete all keys from expiraton cache
|
||||
func XFlush() {
|
||||
func GetAllEntries(prefix string) map[string]interface{} {
|
||||
mux.Lock()
|
||||
result := make(map[string]interface{})
|
||||
for key, timestampedValue := range cache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
result[key] = timestampedValue.value
|
||||
}
|
||||
}
|
||||
mux.Unlock()
|
||||
xMux.Lock()
|
||||
for key, value := range xcache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
xMux.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// Delete all keys from cache
|
||||
func Flush() {
|
||||
mux.Lock()
|
||||
cache = make(map[string]timestampedValue)
|
||||
mux.Unlock()
|
||||
xMux.Lock()
|
||||
defer xMux.Unlock()
|
||||
for _, v := range xcache {
|
||||
if v.timer() != nil {
|
||||
v.timer().Stop()
|
||||
}
|
||||
}
|
||||
xcache = make(map[string]expiringCacheEntry)
|
||||
xMux.Unlock()
|
||||
cMux.Lock()
|
||||
counters = make(map[string]int64)
|
||||
cMux.Unlock()
|
||||
}
|
||||
|
||||
// Delete all keys from cache
|
||||
func Flush() {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
cache = make(map[string]timestampedValue)
|
||||
func CountEntries(prefix string) (result int64) {
|
||||
if _, ok := counters[prefix]; ok {
|
||||
return counters[prefix]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func CountEntries(prefix string) (result int) {
|
||||
// increments the counter for the specified key prefix
|
||||
func count(key string) {
|
||||
if len(key) < PREFIX_LEN {
|
||||
return
|
||||
}
|
||||
cMux.Lock()
|
||||
defer cMux.Unlock()
|
||||
prefix := key[:PREFIX_LEN]
|
||||
if _, ok := counters[prefix]; ok {
|
||||
// increase the value
|
||||
counters[prefix] += 1
|
||||
} else {
|
||||
counters[prefix] = 1
|
||||
}
|
||||
}
|
||||
|
||||
// decrements the counter for the specified key prefix
|
||||
func descount(key string) {
|
||||
if len(key) < PREFIX_LEN {
|
||||
return
|
||||
}
|
||||
cMux.Lock()
|
||||
defer cMux.Unlock()
|
||||
prefix := key[:PREFIX_LEN]
|
||||
if value, ok := counters[prefix]; ok && value > 0 {
|
||||
counters[prefix] -= 1
|
||||
}
|
||||
}
|
||||
|
||||
func GetEntriesKeys(prefix string) (keys []string) {
|
||||
mux.RLock()
|
||||
defer mux.RUnlock()
|
||||
for key, _ := range cache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
result++
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func XCountEntries(prefix string) (result int) {
|
||||
func XGetEntriesKeys(prefix string) (keys []string) {
|
||||
xMux.RLock()
|
||||
defer xMux.RUnlock()
|
||||
for key, _ := range xcache {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
result++
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestFlush(t *testing.T) {
|
||||
a := &myStruct{data: "mama are mere"}
|
||||
a.XCache("mama", 10*time.Second, a)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
XFlush()
|
||||
Flush()
|
||||
b, err := GetXCached("mama")
|
||||
if err == nil || b != nil {
|
||||
t.Error("Error expiring data")
|
||||
@@ -67,7 +67,7 @@ func TestFlush(t *testing.T) {
|
||||
func TestFlushNoTimout(t *testing.T) {
|
||||
a := &myStruct{data: "mama are mere"}
|
||||
a.XCache("mama", 10*time.Second, a)
|
||||
XFlush()
|
||||
Flush()
|
||||
b, err := GetXCached("mama")
|
||||
if err == nil || b != nil {
|
||||
t.Error("Error expiring data")
|
||||
@@ -75,12 +75,12 @@ func TestFlushNoTimout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemKey(t *testing.T) {
|
||||
Cache("t1", "test")
|
||||
if t1, err := GetCached("t1"); err != nil || t1 != "test" {
|
||||
Cache("t11_mm", "test")
|
||||
if t1, err := GetCached("t11_mm"); err != nil || t1 != "test" {
|
||||
t.Error("Error setting cache")
|
||||
}
|
||||
RemKey("t1")
|
||||
if t1, err := GetCached("t1"); err == nil || t1 == "test" {
|
||||
RemKey("t11_mm")
|
||||
if t1, err := GetCached("t11_mm"); err == nil || t1 == "test" {
|
||||
t.Error("Error removing cached key")
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func TestXRemKey(t *testing.T) {
|
||||
if t1, err := GetXCached("mama"); err != nil || t1 != a {
|
||||
t.Error("Error setting xcache")
|
||||
}
|
||||
XRemKey("mama")
|
||||
RemKey("mama")
|
||||
if t1, err := GetXCached("mama"); err == nil || t1 == a {
|
||||
t.Error("Error removing xcached key: ", err, t1)
|
||||
}
|
||||
@@ -134,10 +134,21 @@ func TestXRemPrefixKey(t *testing.T) {
|
||||
a.XCache("x_t1", 10*time.Second, a)
|
||||
a.XCache("y_t1", 10*time.Second, a)
|
||||
|
||||
XRemPrefixKey("x_")
|
||||
RemPrefixKey("x_")
|
||||
_, errX := GetXCached("x_t1")
|
||||
_, errY := GetXCached("y_t1")
|
||||
if errX == nil || errY != nil {
|
||||
t.Error("Error removing prefix: ", errX, errY)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCount(t *testing.T) {
|
||||
Cache("dst_A1", "1")
|
||||
Cache("dst_A2", "2")
|
||||
Cache("rpf_A3", "3")
|
||||
Cache("dst_A4", "4")
|
||||
Cache("dst_A5", "5")
|
||||
if CountEntries("dst_") != 4 {
|
||||
t.Error("Error countiong entries: ", CountEntries("dst_"))
|
||||
}
|
||||
}
|
||||
|
||||
209
cdrc/cdrc.go
209
cdrc/cdrc.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,6 @@ package cdrc
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -31,9 +30,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/cgrates/cgrates/cdrs"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"github.com/howeyc/fsnotify"
|
||||
@@ -44,145 +42,120 @@ const (
|
||||
FS_CSV = "freeswitch_csv"
|
||||
)
|
||||
|
||||
func NewCdrc(config *config.CGRConfig, cdrServer *cdrs.CDRS) (*Cdrc, error) {
|
||||
cdrc := &Cdrc{cgrCfg: config, cdrServer: cdrServer}
|
||||
// Before processing, make sure in and out folders exist
|
||||
for _, dir := range []string{cdrc.cgrCfg.CdrcCdrInDir, cdrc.cgrCfg.CdrcCdrOutDir} {
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("Folder %s does not exist", dir)
|
||||
}
|
||||
func NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField, cdrServer *engine.CDRS) (*Cdrc, error) {
|
||||
if len(csvSep) != 1 {
|
||||
return nil, fmt.Errorf("Unsupported csv separator: %s", csvSep)
|
||||
}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
return nil, err
|
||||
csvSepRune, _ := utf8.DecodeRune([]byte(csvSep))
|
||||
cdrc := &Cdrc{cdrsAddress: cdrsAddress, cdrType: cdrType, cdrInDir: cdrInDir, cdrOutDir: cdrOutDir,
|
||||
cdrSourceId: cdrSourceId, runDelay: runDelay, csvSep: csvSepRune, cdrFields: cdrFields, cdrServer: cdrServer}
|
||||
// Before processing, make sure in and out folders exist
|
||||
for _, dir := range []string{cdrc.cdrInDir, cdrc.cdrOutDir} {
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("Nonexistent folder: %s", dir)
|
||||
}
|
||||
}
|
||||
cdrc.httpClient = new(http.Client)
|
||||
return cdrc, nil
|
||||
}
|
||||
|
||||
type Cdrc struct {
|
||||
cgrCfg *config.CGRConfig
|
||||
cdrServer *cdrs.CDRS
|
||||
cfgCdrFields map[string]string // Key is the name of the field
|
||||
httpClient *http.Client
|
||||
cdrsAddress,
|
||||
cdrType,
|
||||
cdrInDir,
|
||||
cdrOutDir,
|
||||
cdrSourceId string
|
||||
runDelay time.Duration
|
||||
csvSep rune
|
||||
cdrFields map[string][]*utils.RSRField
|
||||
cdrServer *engine.CDRS // Reference towards internal cdrServer if that is the case
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// When called fires up folder monitoring, either automated via inotify or manual by sleeping between processing
|
||||
func (self *Cdrc) Run() error {
|
||||
if self.cgrCfg.CdrcRunDelay == time.Duration(0) { // Automated via inotify
|
||||
if self.runDelay == time.Duration(0) { // Automated via inotify
|
||||
return self.trackCDRFiles()
|
||||
}
|
||||
// No automated, process and sleep approach
|
||||
for {
|
||||
self.processCdrDir()
|
||||
time.Sleep(self.cgrCfg.CdrcRunDelay)
|
||||
time.Sleep(self.runDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// Loads all fields (primary and extra) into cfgCdrFields, do some pre-checks (eg: in case of csv make sure that values are integers)
|
||||
func (self *Cdrc) parseFieldsConfig() error {
|
||||
var err error
|
||||
self.cfgCdrFields = map[string]string{
|
||||
utils.ACCID: self.cgrCfg.CdrcAccIdField,
|
||||
utils.REQTYPE: self.cgrCfg.CdrcReqTypeField,
|
||||
utils.DIRECTION: self.cgrCfg.CdrcDirectionField,
|
||||
utils.TENANT: self.cgrCfg.CdrcTenantField,
|
||||
utils.TOR: self.cgrCfg.CdrcTorField,
|
||||
utils.ACCOUNT: self.cgrCfg.CdrcAccountField,
|
||||
utils.SUBJECT: self.cgrCfg.CdrcSubjectField,
|
||||
utils.DESTINATION: self.cgrCfg.CdrcDestinationField,
|
||||
utils.SETUP_TIME: self.cgrCfg.CdrcSetupTimeField,
|
||||
utils.ANSWER_TIME: self.cgrCfg.CdrcAnswerTimeField,
|
||||
utils.DURATION: self.cgrCfg.CdrcDurationField,
|
||||
}
|
||||
|
||||
// Add extra fields here, config extra fields in the form of []string{"fieldName1:indxInCsv1","fieldName2: indexInCsv2"}
|
||||
for _, fieldWithIdx := range self.cgrCfg.CdrcExtraFields {
|
||||
splt := strings.Split(fieldWithIdx, ":")
|
||||
if len(splt) != 2 {
|
||||
return errors.New("Cannot parse cdrc.extra_fields")
|
||||
}
|
||||
if utils.IsSliceMember(utils.PrimaryCdrFields, splt[0]) {
|
||||
return errors.New("Extra cdrc.extra_fields overwriting primary fields")
|
||||
}
|
||||
self.cfgCdrFields[splt[0]] = splt[1]
|
||||
}
|
||||
// Fields populated, do some sanity checks here
|
||||
for cdrField, cfgVal := range self.cfgCdrFields {
|
||||
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) && !strings.HasPrefix(cfgVal, utils.STATIC_VALUE_PREFIX) {
|
||||
if _, err = strconv.Atoi(cfgVal); err != nil {
|
||||
return fmt.Errorf("Cannot parse configuration field %s into integer", cdrField)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Takes the record out of csv and turns it into http form which can be posted
|
||||
func (self *Cdrc) recordAsStoredCdr(record []string) (*utils.StoredCdr, error) {
|
||||
ratedCdr := &utils.StoredCdr{CdrSource: self.cgrCfg.CdrcSourceId, ExtraFields: map[string]string{}, Cost: -1}
|
||||
func (self *Cdrc) recordToStoredCdr(record []string) (*utils.StoredCdr, error) {
|
||||
storedCdr := &utils.StoredCdr{CdrHost: "0.0.0.0", CdrSource: self.cdrSourceId, ExtraFields: make(map[string]string), Cost: -1}
|
||||
var err error
|
||||
for cfgFieldName, cfgFieldVal := range self.cfgCdrFields {
|
||||
for cfgFieldName, cfgFieldRSRs := range self.cdrFields {
|
||||
var fieldVal string
|
||||
if strings.HasPrefix(cfgFieldVal, utils.STATIC_VALUE_PREFIX) {
|
||||
fieldVal = cfgFieldVal[1:]
|
||||
} else if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cgrCfg.CdrcCdrType) {
|
||||
if cfgFieldIdx, err := strconv.Atoi(cfgFieldVal); err != nil { // Should in theory never happen since we have already parsed config
|
||||
return nil, err
|
||||
} else if len(record) <= cfgFieldIdx {
|
||||
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
|
||||
} else {
|
||||
fieldVal = record[cfgFieldIdx]
|
||||
if utils.IsSliceMember([]string{CSV, FS_CSV}, self.cdrType) {
|
||||
for _, cfgFieldRSR := range cfgFieldRSRs {
|
||||
if strings.HasPrefix(cfgFieldRSR.Id, utils.STATIC_VALUE_PREFIX) {
|
||||
fieldVal += cfgFieldRSR.ParseValue("PLACEHOLDER")
|
||||
} else { // Dynamic value extracted using index
|
||||
if cfgFieldIdx, _ := strconv.Atoi(cfgFieldRSR.Id); len(record) <= cfgFieldIdx {
|
||||
return nil, fmt.Errorf("Ignoring record: %v - cannot extract field %s", record, cfgFieldName)
|
||||
} else {
|
||||
fieldVal += cfgFieldRSR.ParseValue(record[cfgFieldIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Modify here when we add more supported cdr formats
|
||||
fieldVal = "UNKNOWN"
|
||||
}
|
||||
switch cfgFieldName {
|
||||
case utils.ACCID:
|
||||
ratedCdr.CgrId = utils.FSCgrId(fieldVal)
|
||||
ratedCdr.AccId = fieldVal
|
||||
case utils.REQTYPE:
|
||||
ratedCdr.ReqType = fieldVal
|
||||
case utils.DIRECTION:
|
||||
ratedCdr.Direction = fieldVal
|
||||
case utils.TENANT:
|
||||
ratedCdr.Tenant = fieldVal
|
||||
case utils.TOR:
|
||||
ratedCdr.TOR = fieldVal
|
||||
storedCdr.TOR = fieldVal
|
||||
case utils.ACCID:
|
||||
storedCdr.AccId = fieldVal
|
||||
case utils.REQTYPE:
|
||||
storedCdr.ReqType = fieldVal
|
||||
case utils.DIRECTION:
|
||||
storedCdr.Direction = fieldVal
|
||||
case utils.TENANT:
|
||||
storedCdr.Tenant = fieldVal
|
||||
case utils.CATEGORY:
|
||||
storedCdr.Category = fieldVal
|
||||
case utils.ACCOUNT:
|
||||
ratedCdr.Account = fieldVal
|
||||
storedCdr.Account = fieldVal
|
||||
case utils.SUBJECT:
|
||||
ratedCdr.Subject = fieldVal
|
||||
storedCdr.Subject = fieldVal
|
||||
case utils.DESTINATION:
|
||||
ratedCdr.Destination = fieldVal
|
||||
storedCdr.Destination = fieldVal
|
||||
case utils.SETUP_TIME:
|
||||
if ratedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error())
|
||||
if storedCdr.SetupTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error())
|
||||
}
|
||||
case utils.ANSWER_TIME:
|
||||
if ratedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse answer time field, err: %s", err.Error())
|
||||
if storedCdr.AnswerTime, err = utils.ParseTimeDetectLayout(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse answer time field with value: %s, err: %s", fieldVal, err.Error())
|
||||
}
|
||||
case utils.DURATION:
|
||||
if ratedCdr.Duration, err = utils.ParseDurationWithSecs(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse duration field, err: %s", err.Error())
|
||||
case utils.USAGE:
|
||||
if storedCdr.Usage, err = utils.ParseDurationWithNanosecs(fieldVal); err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse duration field with value: %s, err: %s", fieldVal, err.Error())
|
||||
}
|
||||
default: // Extra fields will not match predefined so they all show up here
|
||||
ratedCdr.ExtraFields[cfgFieldName] = fieldVal
|
||||
storedCdr.ExtraFields[cfgFieldName] = fieldVal
|
||||
}
|
||||
|
||||
}
|
||||
return ratedCdr, nil
|
||||
storedCdr.CgrId = utils.Sha1(storedCdr.AccId, storedCdr.SetupTime.String())
|
||||
return storedCdr, nil
|
||||
}
|
||||
|
||||
// One run over the CDR folder
|
||||
func (self *Cdrc) processCdrDir() error {
|
||||
engine.Logger.Info(fmt.Sprintf("<Cdrc> Parsing folder %s for CDR files.", self.cgrCfg.CdrcCdrInDir))
|
||||
filesInDir, _ := ioutil.ReadDir(self.cgrCfg.CdrcCdrInDir)
|
||||
engine.Logger.Info(fmt.Sprintf("<Cdrc> Parsing folder %s for CDR files.", self.cdrInDir))
|
||||
filesInDir, _ := ioutil.ReadDir(self.cdrInDir)
|
||||
for _, file := range filesInDir {
|
||||
if self.cgrCfg.CdrcCdrType != FS_CSV || path.Ext(file.Name()) != ".csv" {
|
||||
if err := self.processFile(path.Join(self.cgrCfg.CdrcCdrInDir, file.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
if self.cdrType != FS_CSV || path.Ext(file.Name()) != ".csv" {
|
||||
go func() { //Enable async processing here
|
||||
if err := self.processFile(path.Join(self.cdrInDir, file.Name())); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", file, err.Error()))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -195,18 +168,20 @@ func (self *Cdrc) trackCDRFiles() (err error) {
|
||||
return
|
||||
}
|
||||
defer watcher.Close()
|
||||
err = watcher.Watch(self.cgrCfg.CdrcCdrInDir)
|
||||
err = watcher.Watch(self.cdrInDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
engine.Logger.Info(fmt.Sprintf("<Cdrc> Monitoring %s for file moves.", self.cgrCfg.CdrcCdrInDir))
|
||||
engine.Logger.Info(fmt.Sprintf("<Cdrc> Monitoring %s for file moves.", self.cdrInDir))
|
||||
for {
|
||||
select {
|
||||
case ev := <-watcher.Event:
|
||||
if ev.IsCreate() && (self.cgrCfg.CdrcCdrType != FS_CSV || path.Ext(ev.Name) != ".csv") {
|
||||
if err = self.processFile(ev.Name); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", ev.Name, err.Error()))
|
||||
}
|
||||
if ev.IsCreate() && (self.cdrType != FS_CSV || path.Ext(ev.Name) != ".csv") {
|
||||
go func() { //Enable async processing here
|
||||
if err = self.processFile(ev.Name); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("Processing file %s, error: %s", ev.Name, err.Error()))
|
||||
}
|
||||
}()
|
||||
}
|
||||
case err := <-watcher.Error:
|
||||
engine.Logger.Err(fmt.Sprintf("Inotify error: %s", err.Error()))
|
||||
@@ -225,37 +200,43 @@ func (self *Cdrc) processFile(filePath string) error {
|
||||
return err
|
||||
}
|
||||
csvReader := csv.NewReader(bufio.NewReader(file))
|
||||
csvReader.Comma = self.csvSep
|
||||
procRowNr := 0
|
||||
timeStart := time.Now()
|
||||
for {
|
||||
record, err := csvReader.Read()
|
||||
if err != nil && err == io.EOF {
|
||||
break // End of file
|
||||
} else if err != nil {
|
||||
}
|
||||
procRowNr += 1 // Only increase if not end of file
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
|
||||
continue // Other csv related errors, ignore
|
||||
}
|
||||
rawCdr, err := self.recordAsStoredCdr(record)
|
||||
storedCdr, err := self.recordToStoredCdr(record)
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Error in csv file: %s", err.Error()))
|
||||
continue
|
||||
}
|
||||
if self.cgrCfg.CdrcCdrs == utils.INTERNAL {
|
||||
if err := self.cdrServer.ProcessRawCdr(rawCdr); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, error: %s", err.Error()))
|
||||
if self.cdrsAddress == utils.INTERNAL {
|
||||
if err := self.cdrServer.ProcessCdr(storedCdr); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, row: %d, error: %s", procRowNr, err.Error()))
|
||||
continue
|
||||
}
|
||||
} else { // CDRs listening on IP
|
||||
if _, err := self.httpClient.PostForm(fmt.Sprintf("http://%s/cgr", self.cgrCfg.HTTPListen), rawCdr.AsRawCdrHttpForm()); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, error: %s", err.Error()))
|
||||
if _, err := self.httpClient.PostForm(fmt.Sprintf("http://%s/cgr", self.cdrsAddress), storedCdr.AsHttpForm()); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<Cdrc> Failed posting CDR, row: %d, error: %s", procRowNr, err.Error()))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finished with file, move it to processed folder
|
||||
newPath := path.Join(self.cgrCfg.CdrcCdrOutDir, fn)
|
||||
newPath := path.Join(self.cdrOutDir, fn)
|
||||
if err := os.Rename(filePath, newPath); err != nil {
|
||||
engine.Logger.Err(err.Error())
|
||||
return err
|
||||
}
|
||||
engine.Logger.Info(fmt.Sprintf("Finished processing %s, moved to %s", fn, newPath))
|
||||
engine.Logger.Info(fmt.Sprintf("Finished processing %s, moved to %s. Total records processed: %d, run duration: %s",
|
||||
fn, newPath, procRowNr, time.Now().Sub(timeStart)))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -56,7 +56,7 @@ var waitRater = flag.Int("wait_rater", 300, "Number of miliseconds to wait for r
|
||||
|
||||
func init() {
|
||||
cfgPath = path.Join(*dataDir, "conf", "samples", "apier_local_test.cfg")
|
||||
cfg, _ = config.NewCGRConfig(&cfgPath)
|
||||
cfg, _ = config.NewCGRConfigFromFile(&cfgPath)
|
||||
}
|
||||
|
||||
var fileContent1 = `accid11,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
|
||||
@@ -70,13 +70,18 @@ accid22,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:0
|
||||
#accid1,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1
|
||||
accid23,prepaid,out,cgrates.org,call,1001,1001,+4986517174963,2013-02-03 19:54:00,62,supplier1,172.16.1.1`
|
||||
|
||||
var fileContent3 = `accid31;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1
|
||||
accid32;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1
|
||||
#accid1;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1
|
||||
accid33;prepaid;out;cgrates.org;call;1001;1001;+4986517174963;2013-02-03 19:54:00;62;supplier1;172.16.1.1`
|
||||
|
||||
func startEngine() error {
|
||||
enginePath, err := exec.LookPath("cgr-engine")
|
||||
if err != nil {
|
||||
return errors.New("Cannot find cgr-engine executable")
|
||||
}
|
||||
stopEngine()
|
||||
engine := exec.Command(enginePath, "-cdrs", "-config", cfgPath)
|
||||
engine := exec.Command(enginePath, "-config", cfgPath)
|
||||
if err := engine.Start(); err != nil {
|
||||
return fmt.Errorf("Cannot start cgr-engine: %s", err.Error())
|
||||
}
|
||||
@@ -102,7 +107,7 @@ func TestEmptyTables(t *testing.T) {
|
||||
} else {
|
||||
mysql = d.(*engine.MySQLStorage)
|
||||
}
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_COSTDETAILS_TABLES_SQL, engine.CREATE_MEDIATOR_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
|
||||
for _, scriptName := range []string{engine.CREATE_CDRS_TABLES_SQL, engine.CREATE_TARIFFPLAN_TABLES_SQL} {
|
||||
if err := mysql.CreateTablesFromScript(path.Join(*dataDir, "storage", *storDbType, scriptName)); err != nil {
|
||||
t.Fatal("Error on mysql creation: ", err.Error())
|
||||
return // No point in going further
|
||||
@@ -126,6 +131,12 @@ func TestCreateCdrFiles(t *testing.T) {
|
||||
if err := os.MkdirAll(cfg.CdrcCdrInDir, 0755); err != nil {
|
||||
t.Fatal("Error creating folder: ", cfg.CdrcCdrInDir, err)
|
||||
}
|
||||
if err := os.RemoveAll(cfg.CdrcCdrOutDir); err != nil {
|
||||
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
|
||||
}
|
||||
if err := os.MkdirAll(cfg.CdrcCdrOutDir, 0755); err != nil {
|
||||
t.Fatal("Error creating folder: ", cfg.CdrcCdrOutDir, err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file1.csv"), []byte(fileContent1), 0644); err != nil {
|
||||
t.Fatal(err.Error)
|
||||
}
|
||||
@@ -144,7 +155,45 @@ func TestProcessCdrDir(t *testing.T) {
|
||||
if err := startEngine(); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
cdrc, err := NewCdrc(cfg, nil)
|
||||
cdrc, err := NewCdrc(cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, cfg.CdrcCsvSep,
|
||||
cfg.CdrcCdrFields, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if err := cdrc.processCdrDir(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
stopEngine()
|
||||
}
|
||||
|
||||
// Creates cdr files and starts the engine
|
||||
func TestCreateCdr3File(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
if err := os.RemoveAll(cfg.CdrcCdrInDir); err != nil {
|
||||
t.Fatal("Error removing folder: ", cfg.CdrcCdrInDir, err)
|
||||
}
|
||||
if err := os.MkdirAll(cfg.CdrcCdrInDir, 0755); err != nil {
|
||||
t.Fatal("Error creating folder: ", cfg.CdrcCdrInDir, err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(cfg.CdrcCdrInDir, "file3.csv"), []byte(fileContent3), 0644); err != nil {
|
||||
t.Fatal(err.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessCdr3Dir(t *testing.T) {
|
||||
if !*testLocal {
|
||||
return
|
||||
}
|
||||
if cfg.CdrcCdrs == utils.INTERNAL { // For now we only test over network
|
||||
return
|
||||
}
|
||||
if err := startEngine(); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
cdrc, err := NewCdrc(cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, ";",
|
||||
cfg.CdrcCdrFields, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
@@ -19,90 +19,173 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package cdrc
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
//"encoding/csv"
|
||||
//"fmt"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
//"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func TestParseFieldsConfig(t *testing.T) {
|
||||
// Test default config
|
||||
func TestRecordForkCdr(t *testing.T) {
|
||||
cgrConfig, _ := config.NewDefaultCGRConfig()
|
||||
// Test primary field index definition
|
||||
cgrConfig.CdrcAccIdField = "detect_me"
|
||||
cdrc := &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err == nil {
|
||||
t.Error("Failed detecting error in accounting id definition", err)
|
||||
}
|
||||
cgrConfig.CdrcAccIdField = "^static_val"
|
||||
cgrConfig.CdrcSubjectField = "1"
|
||||
cdrc = &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
t.Error("Failed to corectly parse primary fields %v", cdrc.cfgCdrFields)
|
||||
}
|
||||
cgrConfig.CdrcExtraFields = []string{"^static_val:orig_ip"}
|
||||
// Test extra field index definition
|
||||
cgrConfig.CdrcAccIdField = "0" // Put back as int
|
||||
cgrConfig.CdrcExtraFields = []string{"supplier1", "orig_ip:11"}
|
||||
cdrc = &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err == nil {
|
||||
t.Error("Failed detecting error in extra fields definition", err)
|
||||
}
|
||||
cgrConfig.CdrcExtraFields = []string{"supplier1:^top_supplier", "orig_ip:11"}
|
||||
cdrc = &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
t.Errorf("Failed to corectly parse extra fields %v", cdrc.cfgCdrFields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordAsStoredCdr(t *testing.T) {
|
||||
cgrConfig, _ := config.NewDefaultCGRConfig()
|
||||
cgrConfig.CdrcExtraFields = []string{"supplier:11"}
|
||||
cdrc := &Cdrc{cgrCfg: cgrConfig}
|
||||
if err := cdrc.parseFieldsConfig(); err != nil {
|
||||
t.Error("Failed parsing default fieldIndexesFromConfig", err)
|
||||
}
|
||||
cgrConfig.CdrcCdrFields["supplier"] = []*utils.RSRField{&utils.RSRField{Id: "14"}}
|
||||
csvSepRune, _ := utf8.DecodeRune([]byte(cgrConfig.CdrcCsvSep))
|
||||
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, csvSepRune,
|
||||
cgrConfig.CdrcCdrFields, new(engine.CDRS), nil}
|
||||
cdrRow := []string{"firstField", "secondField"}
|
||||
_, err := cdrc.recordAsStoredCdr(cdrRow)
|
||||
_, err := cdrc.recordToStoredCdr(cdrRow)
|
||||
if err == nil {
|
||||
t.Error("Failed to corectly detect missing fields from record")
|
||||
}
|
||||
cdrRow = []string{"acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963", "2013-02-03 19:50:00", "2013-02-03 19:54:00", "62",
|
||||
"supplier1", "172.16.1.1"}
|
||||
rtCdr, err := cdrc.recordAsStoredCdr(cdrRow)
|
||||
cdrRow = []string{"ignored", "ignored", utils.VOICE, "acc1", "prepaid", "*out", "cgrates.org", "call", "1001", "1001", "+4986517174963",
|
||||
"2013-02-03 19:50:00", "2013-02-03 19:54:00", "62000000000", "supplier1", "172.16.1.1"}
|
||||
rtCdr, err := cdrc.recordToStoredCdr(cdrRow)
|
||||
if err != nil {
|
||||
t.Error("Failed to parse CDR in rated cdr", err)
|
||||
}
|
||||
expectedCdr := &utils.StoredCdr{
|
||||
CgrId: utils.FSCgrId(cdrRow[0]),
|
||||
AccId: cdrRow[0],
|
||||
CgrId: utils.Sha1(cdrRow[3], time.Date(2013, 2, 3, 19, 50, 0, 0, time.UTC).String()),
|
||||
TOR: cdrRow[2],
|
||||
AccId: cdrRow[3],
|
||||
CdrHost: "0.0.0.0", // Got it over internal interface
|
||||
CdrSource: cgrConfig.CdrcSourceId,
|
||||
ReqType: cdrRow[1],
|
||||
Direction: cdrRow[2],
|
||||
Tenant: cdrRow[3],
|
||||
TOR: cdrRow[4],
|
||||
Account: cdrRow[5],
|
||||
Subject: cdrRow[6],
|
||||
Destination: cdrRow[7],
|
||||
ReqType: cdrRow[4],
|
||||
Direction: cdrRow[5],
|
||||
Tenant: cdrRow[6],
|
||||
Category: cdrRow[7],
|
||||
Account: cdrRow[8],
|
||||
Subject: cdrRow[9],
|
||||
Destination: cdrRow[10],
|
||||
SetupTime: time.Date(2013, 2, 3, 19, 50, 0, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 2, 3, 19, 54, 0, 0, time.UTC),
|
||||
Duration: time.Duration(62) * time.Second,
|
||||
Usage: time.Duration(62) * time.Second,
|
||||
ExtraFields: map[string]string{"supplier": "supplier1"},
|
||||
Cost: -1,
|
||||
}
|
||||
if !reflect.DeepEqual(expectedCdr, rtCdr) {
|
||||
t.Errorf("Expected: \n%v, \nreceived: \n%v", expectedCdr, rtCdr)
|
||||
}
|
||||
/*
|
||||
if cdrAsForm.Get(utils.CDRSOURCE) != cgrConfig.CdrcSourceId {
|
||||
t.Error("Unexpected cdrsource received", cdrAsForm.Get(utils.CDRSOURCE))
|
||||
}
|
||||
if cdrAsForm.Get(utils.REQTYPE) != "prepaid" {
|
||||
t.Error("Unexpected CDR value received", cdrAsForm.Get(utils.REQTYPE))
|
||||
}
|
||||
if cdrAsForm.Get("supplier") != "supplier1" {
|
||||
t.Error("Unexpected CDR value received", cdrAsForm.Get("supplier"))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
func TestDnTdmCdrs(t *testing.T) {
|
||||
tdmCdrs := `
|
||||
49773280254,0049LN130676000285,N_IP_0676_00-Internet 0676 WRAP 13,02.07.2014 15:24:40,02.07.2014 15:24:40,1,25,Peak,0.000000,49DE13
|
||||
49893252121,0049651515477,N_MO_MRAP_00-WRAP Mobile,02.07.2014 15:24:41,02.07.2014 15:24:41,1,8,Peak,0.003920,49651
|
||||
49497361022,0049LM0409005226,N_MO_MTMB_00-RW-Mobile,02.07.2014 15:24:41,02.07.2014 15:24:41,1,43,Peak,0.021050,49MTMB
|
||||
`
|
||||
cgrConfig, _ := config.NewDefaultCGRConfig()
|
||||
eCdrs := []*utils.StoredCdr{
|
||||
&utils.StoredCdr{
|
||||
CgrId: utils.Sha1("49773280254", time.Date(2014, 7, 2, 15, 24, 40, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE,
|
||||
AccId: "49773280254",
|
||||
CdrHost: "0.0.0.0",
|
||||
CdrSource: cgrConfig.CdrcSourceId,
|
||||
ReqType: "rated",
|
||||
Direction: "*out",
|
||||
Tenant: "sip.test.deanconnect.nl",
|
||||
Category: "call",
|
||||
Account: "+49773280254",
|
||||
Subject: "+49773280254",
|
||||
Destination: "+49676000285",
|
||||
SetupTime: time.Date(2014, 7, 2, 15, 24, 40, 0, time.UTC),
|
||||
AnswerTime: time.Date(2014, 7, 2, 15, 24, 40, 0, time.UTC),
|
||||
Usage: time.Duration(25) * time.Second,
|
||||
Cost: -1,
|
||||
},
|
||||
&utils.StoredCdr{
|
||||
CgrId: utils.Sha1("49893252121", time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE,
|
||||
AccId: "49893252121",
|
||||
CdrHost: "0.0.0.0",
|
||||
CdrSource: cgrConfig.CdrcSourceId,
|
||||
ReqType: "rated",
|
||||
Direction: "*out",
|
||||
Tenant: "sip.test.deanconnect.nl",
|
||||
Category: "call",
|
||||
Account: "+49893252121",
|
||||
Subject: "+49893252121",
|
||||
Destination: "+49651515477",
|
||||
SetupTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
|
||||
AnswerTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
|
||||
Usage: time.Duration(8) * time.Second,
|
||||
Cost: -1,
|
||||
},
|
||||
&utils.StoredCdr{
|
||||
CgrId: utils.Sha1("49497361022", time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE,
|
||||
AccId: "49497361022",
|
||||
CdrHost: "0.0.0.0",
|
||||
CdrSource: cgrConfig.CdrcSourceId,
|
||||
ReqType: "rated",
|
||||
Direction: "*out",
|
||||
Tenant: "sip.test.deanconnect.nl",
|
||||
Category: "call",
|
||||
Account: "+49497361022",
|
||||
Subject: "+49497361022",
|
||||
Destination: "+499005226",
|
||||
SetupTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
|
||||
AnswerTime: time.Date(2014, 7, 2, 15, 24, 41, 0, time.UTC),
|
||||
Usage: time.Duration(43) * time.Second,
|
||||
Cost: -1,
|
||||
},
|
||||
}
|
||||
torFld, _ := utils.NewRSRField("^*voice")
|
||||
acntFld, _ := utils.NewRSRField(`~0:s/^([1-9]\d+)$/+$1/`)
|
||||
reqTypeFld, _ := utils.NewRSRField("^rated")
|
||||
dirFld, _ := utils.NewRSRField("^*out")
|
||||
tenantFld, _ := utils.NewRSRField("^sip.test.deanconnect.nl")
|
||||
categFld, _ := utils.NewRSRField("^call")
|
||||
dstFld, _ := utils.NewRSRField(`~1:s/^00(\d+)(?:[a-zA-Z].{3})*0*([1-9]\d+)$/+$1$2/`)
|
||||
usageFld, _ := utils.NewRSRField(`~6:s/^(\d+)$/${1}s/`)
|
||||
cgrConfig.CdrcCdrFields = map[string]*utils.RSRField{
|
||||
utils.TOR: torFld,
|
||||
utils.ACCID: &utils.RSRField{Id: "0"},
|
||||
utils.REQTYPE: reqTypeFld,
|
||||
utils.DIRECTION: dirFld,
|
||||
utils.TENANT: tenantFld,
|
||||
utils.CATEGORY: categFld,
|
||||
utils.ACCOUNT: acntFld,
|
||||
utils.SUBJECT: acntFld,
|
||||
utils.DESTINATION: dstFld,
|
||||
utils.SETUP_TIME: &utils.RSRField{Id: "4"},
|
||||
utils.ANSWER_TIME: &utils.RSRField{Id: "4"},
|
||||
utils.USAGE: usageFld,
|
||||
}
|
||||
cdrc := &Cdrc{cgrConfig.CdrcCdrs, cgrConfig.CdrcCdrType, cgrConfig.CdrcCdrInDir, cgrConfig.CdrcCdrOutDir, cgrConfig.CdrcSourceId, cgrConfig.CdrcRunDelay, ',',
|
||||
cgrConfig.CdrcCdrFields, new(cdrs.CDRS), nil}
|
||||
cdrsContent := bytes.NewReader([]byte(tdmCdrs))
|
||||
csvReader := csv.NewReader(cdrsContent)
|
||||
cdrs := make([]*utils.StoredCdr, 0)
|
||||
for {
|
||||
cdrCsv, err := csvReader.Read()
|
||||
if err != nil && err == io.EOF {
|
||||
break // End of file
|
||||
} else if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
if cdr, err := cdrc.recordToStoredCdr(cdrCsv); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else {
|
||||
cdrs = append(cdrs, cdr)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(eCdrs, cdrs) {
|
||||
for _, ecdr := range eCdrs {
|
||||
fmt.Printf("Cdr expected: %+v\n", ecdr)
|
||||
}
|
||||
for _, cdr := range cdrs {
|
||||
fmt.Printf("Cdr processed: %+v\n", cdr)
|
||||
}
|
||||
t.Errorf("Expecting: %+v, received: %+v", eCdrs, cdrs)
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,10 +19,496 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package cdre
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CdrWriter interface {
|
||||
WriteCdr(cdr *utils.StoredCdr) string
|
||||
Close()
|
||||
const (
|
||||
COST_DETAILS = "cost_details"
|
||||
FILLER = "filler"
|
||||
CONSTANT = "constant"
|
||||
METATAG = "metatag"
|
||||
CONCATENATED_CDRFIELD = "concatenated_cdrfield"
|
||||
COMBIMED = "combimed"
|
||||
DATETIME = "datetime"
|
||||
HTTP_POST = "http_post"
|
||||
META_EXPORTID = "export_id"
|
||||
META_TIMENOW = "time_now"
|
||||
META_FIRSTCDRATIME = "first_cdr_atime"
|
||||
META_LASTCDRATIME = "last_cdr_atime"
|
||||
META_NRCDRS = "cdrs_number"
|
||||
META_DURCDRS = "cdrs_duration"
|
||||
META_SMSUSAGE = "sms_usage"
|
||||
META_DATAUSAGE = "data_usage"
|
||||
META_COSTCDRS = "cdrs_cost"
|
||||
META_MASKDESTINATION = "mask_destination"
|
||||
META_FORMATCOST = "format_cost"
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
func NewCdrExporter(cdrs []*utils.StoredCdr, logDb engine.LogStorage, exportTpl *config.CdreConfig, cdrFormat string, fieldSeparator rune, exportId string,
|
||||
dataUsageMultiplyFactor, costMultiplyFactor float64, costShiftDigits, roundDecimals, cgrPrecision int, maskDestId string, maskLen int, httpSkipTlsCheck bool) (*CdrExporter, error) {
|
||||
if len(cdrs) == 0 { // Nothing to export
|
||||
return nil, nil
|
||||
}
|
||||
cdre := &CdrExporter{
|
||||
cdrs: cdrs,
|
||||
logDb: logDb,
|
||||
exportTemplate: exportTpl,
|
||||
cdrFormat: cdrFormat,
|
||||
fieldSeparator: fieldSeparator,
|
||||
exportId: exportId,
|
||||
dataUsageMultiplyFactor: dataUsageMultiplyFactor,
|
||||
costMultiplyFactor: costMultiplyFactor,
|
||||
costShiftDigits: costShiftDigits,
|
||||
roundDecimals: roundDecimals,
|
||||
cgrPrecision: cgrPrecision,
|
||||
maskDestId: maskDestId,
|
||||
httpSkipTlsCheck: httpSkipTlsCheck,
|
||||
maskLen: maskLen,
|
||||
negativeExports: make(map[string]string),
|
||||
}
|
||||
if err := cdre.processCdrs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cdre, nil
|
||||
}
|
||||
|
||||
type CdrExporter struct {
|
||||
cdrs []*utils.StoredCdr
|
||||
logDb engine.LogStorage // Used to extract cost_details if these are requested
|
||||
exportTemplate *config.CdreConfig
|
||||
cdrFormat string // csv, fwv
|
||||
fieldSeparator rune
|
||||
exportId string // Unique identifier or this export
|
||||
dataUsageMultiplyFactor, costMultiplyFactor float64
|
||||
costShiftDigits, roundDecimals, cgrPrecision int
|
||||
maskDestId string
|
||||
maskLen int
|
||||
httpSkipTlsCheck bool
|
||||
header, trailer []string // Header and Trailer fields
|
||||
content [][]string // Rows of cdr fields
|
||||
firstCdrATime, lastCdrATime time.Time
|
||||
numberOfRecords int
|
||||
totalDuration, totalDataUsage, totalSmsUsage time.Duration
|
||||
|
||||
totalCost float64
|
||||
firstExpOrderId, lastExpOrderId int64
|
||||
positiveExports []string // CGRIds of successfully exported CDRs
|
||||
negativeExports map[string]string // CgrIds of failed exports
|
||||
}
|
||||
|
||||
// Return Json marshaled callCost attached to
|
||||
// Keep it separately so we test only this part in local tests
|
||||
func (cdre *CdrExporter) getCdrCostDetails(cgrId, runId string) (string, error) {
|
||||
cc, err := cdre.logDb.GetCallCostLog(cgrId, "", runId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if cc == nil {
|
||||
return "", nil
|
||||
}
|
||||
ccJson, _ := json.Marshal(cc)
|
||||
return string(ccJson), nil
|
||||
}
|
||||
|
||||
func (cdre *CdrExporter) getCombimedCdrFieldVal(processedCdr *utils.StoredCdr, filterRule, fieldRule *utils.RSRField) (string, error) {
|
||||
fltrPass, ftrPassValue := processedCdr.PassesFieldFilter(filterRule)
|
||||
if !fltrPass {
|
||||
return "", nil
|
||||
}
|
||||
for _, cdr := range cdre.cdrs {
|
||||
if cdr.CgrId != processedCdr.CgrId {
|
||||
continue // We only care about cdrs with same primary cdr behind
|
||||
}
|
||||
if cdr.FieldAsString(&utils.RSRField{Id: filterRule.Id}) == ftrPassValue {
|
||||
return cdr.FieldAsString(fieldRule), nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Check if the destination should be masked in output
|
||||
func (cdre *CdrExporter) maskedDestination(destination string) bool {
|
||||
if len(cdre.maskDestId) != 0 && engine.CachedDestHasPrefix(cdre.maskDestId, destination) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cdre *CdrExporter) getDateTimeFieldVal(cdr *utils.StoredCdr, fltrRl, fieldRl *utils.RSRField, layout string) (string, error) {
|
||||
if fieldRl == nil {
|
||||
return "", nil
|
||||
}
|
||||
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
|
||||
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
|
||||
}
|
||||
if len(layout) == 0 {
|
||||
layout = time.RFC3339
|
||||
}
|
||||
if dtFld, err := utils.ParseTimeDetectLayout(cdr.FieldAsString(fieldRl)); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return dtFld.Format(layout), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the value specified by cfgHdr out of cdr
|
||||
func (cdre *CdrExporter) cdrFieldValue(cdr *utils.StoredCdr, fltrRl, rsrFld *utils.RSRField, layout string) (string, error) {
|
||||
if rsrFld == nil {
|
||||
return "", nil
|
||||
}
|
||||
if fltrPass, _ := cdr.PassesFieldFilter(fltrRl); !fltrPass {
|
||||
return "", fmt.Errorf("Field: %s not matching filter rule %v", fltrRl.Id, fltrRl)
|
||||
}
|
||||
if len(layout) == 0 {
|
||||
layout = time.RFC3339
|
||||
}
|
||||
var cdrVal string
|
||||
switch rsrFld.Id {
|
||||
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
|
||||
if cdrVal, err = cdre.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case utils.COST:
|
||||
cdrVal = cdr.FormatCost(cdre.costShiftDigits, cdre.roundDecimals)
|
||||
case utils.USAGE:
|
||||
cdrVal = cdr.FormatUsage(layout)
|
||||
case utils.SETUP_TIME:
|
||||
cdrVal = cdr.SetupTime.Format(layout)
|
||||
case utils.ANSWER_TIME: // Format time based on layout
|
||||
cdrVal = cdr.AnswerTime.Format(layout)
|
||||
case utils.DESTINATION:
|
||||
cdrVal = cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION})
|
||||
if cdre.maskLen != -1 && cdre.maskedDestination(cdrVal) {
|
||||
cdrVal = MaskDestination(cdrVal, cdre.maskLen)
|
||||
}
|
||||
default:
|
||||
cdrVal = cdr.FieldAsString(rsrFld)
|
||||
}
|
||||
return rsrFld.ParseValue(cdrVal), nil
|
||||
}
|
||||
|
||||
// Handle various meta functions used in header/trailer
|
||||
func (cdre *CdrExporter) metaHandler(tag, arg string) (string, error) {
|
||||
switch tag {
|
||||
case META_EXPORTID:
|
||||
return cdre.exportId, nil
|
||||
case META_TIMENOW:
|
||||
return time.Now().Format(arg), nil
|
||||
case META_FIRSTCDRATIME:
|
||||
return cdre.firstCdrATime.Format(arg), nil
|
||||
case META_LASTCDRATIME:
|
||||
return cdre.lastCdrATime.Format(arg), nil
|
||||
case META_NRCDRS:
|
||||
return strconv.Itoa(cdre.numberOfRecords), nil
|
||||
case META_DURCDRS:
|
||||
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
|
||||
emulatedCdr := &utils.StoredCdr{TOR: utils.VOICE, Usage: cdre.totalDuration}
|
||||
return emulatedCdr.FormatUsage(arg), nil
|
||||
case META_SMSUSAGE:
|
||||
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
|
||||
emulatedCdr := &utils.StoredCdr{TOR: utils.SMS, Usage: cdre.totalSmsUsage}
|
||||
return emulatedCdr.FormatUsage(arg), nil
|
||||
case META_DATAUSAGE:
|
||||
//return strconv.FormatFloat(cdre.totalDuration.Seconds(), 'f', -1, 64), nil
|
||||
emulatedCdr := &utils.StoredCdr{TOR: utils.DATA, Usage: cdre.totalDataUsage}
|
||||
return emulatedCdr.FormatUsage(arg), nil
|
||||
case META_COSTCDRS:
|
||||
return strconv.FormatFloat(utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
|
||||
case META_MASKDESTINATION:
|
||||
if cdre.maskedDestination(arg) {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported METATAG: %s", tag)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Compose and cache the header
|
||||
func (cdre *CdrExporter) composeHeader() error {
|
||||
for _, cfgFld := range cdre.exportTemplate.HeaderFields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER:
|
||||
outVal = cfgFld.Value
|
||||
cfgFld.Padding = "right"
|
||||
case CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case METATAG:
|
||||
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error()))
|
||||
return err
|
||||
}
|
||||
fmtOut := outVal
|
||||
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, field %s, error: %s", cfgFld.Name, err.Error()))
|
||||
return err
|
||||
}
|
||||
cdre.header = append(cdre.header, fmtOut)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compose and cache the trailer
|
||||
func (cdre *CdrExporter) composeTrailer() error {
|
||||
for _, cfgFld := range cdre.exportTemplate.TrailerFields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER:
|
||||
outVal = cfgFld.Value
|
||||
cfgFld.Padding = "right"
|
||||
case CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case METATAG:
|
||||
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error()))
|
||||
return err
|
||||
}
|
||||
fmtOut := outVal
|
||||
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, field: %s, error: %s", cfgFld.Name, err.Error()))
|
||||
return err
|
||||
}
|
||||
cdre.trailer = append(cdre.trailer, fmtOut)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write individual cdr into content buffer, build stats
|
||||
func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error {
|
||||
if cdr == nil || len(cdr.CgrId) == 0 { // We do not export empty CDRs
|
||||
return nil
|
||||
}
|
||||
if cdre.dataUsageMultiplyFactor != 0.0 && cdr.TOR == utils.DATA {
|
||||
cdr.UsageMultiply(cdre.dataUsageMultiplyFactor, cdre.cgrPrecision)
|
||||
}
|
||||
if cdre.costMultiplyFactor != 0.0 {
|
||||
cdr.CostMultiply(cdre.costMultiplyFactor, cdre.cgrPrecision)
|
||||
}
|
||||
var err error
|
||||
cdrRow := make([]string, len(cdre.exportTemplate.ContentFields))
|
||||
for idx, cfgFld := range cdre.exportTemplate.ContentFields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER:
|
||||
outVal = cfgFld.Value
|
||||
cfgFld.Padding = "right"
|
||||
case CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case utils.CDRFIELD:
|
||||
outVal, err = cdre.cdrFieldValue(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField(), cfgFld.Layout)
|
||||
case DATETIME:
|
||||
outVal, err = cdre.getDateTimeFieldVal(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField(), cfgFld.Layout)
|
||||
case HTTP_POST:
|
||||
var outValByte []byte
|
||||
if outValByte, err = utils.HttpJsonPost(cfgFld.Value, cdre.httpSkipTlsCheck, cdr); err == nil {
|
||||
outVal = string(outValByte)
|
||||
if len(outVal) == 0 && cfgFld.Mandatory {
|
||||
err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Name)
|
||||
}
|
||||
}
|
||||
case COMBIMED:
|
||||
outVal, err = cdre.getCombimedCdrFieldVal(cdr, cfgFld.Filter, cfgFld.ValueAsRSRField())
|
||||
case CONCATENATED_CDRFIELD:
|
||||
for _, fld := range strings.Split(cfgFld.Value, ",") {
|
||||
if fldOut, err := cdre.cdrFieldValue(cdr, cfgFld.Filter, &utils.RSRField{Id: fld}, cfgFld.Layout); err != nil {
|
||||
break // The error will be reported bellow
|
||||
} else {
|
||||
outVal += fldOut
|
||||
}
|
||||
}
|
||||
case METATAG:
|
||||
if cfgFld.Value == META_MASKDESTINATION {
|
||||
outVal, err = cdre.metaHandler(cfgFld.Value, cdr.FieldAsString(&utils.RSRField{Id: utils.DESTINATION}))
|
||||
} else {
|
||||
outVal, err = cdre.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
|
||||
return err
|
||||
}
|
||||
fmtOut := outVal
|
||||
if fmtOut, err = FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s, runid: %s, fieldName: %s, fieldValue: %s, error: %s", cdr.CgrId, cdr.MediationRunId, cfgFld.Name, outVal, err.Error()))
|
||||
return err
|
||||
}
|
||||
cdrRow[idx] += fmtOut
|
||||
}
|
||||
if len(cdrRow) == 0 { // No CDR data, most likely no configuration fields defined
|
||||
return nil
|
||||
} else {
|
||||
cdre.content = append(cdre.content, cdrRow)
|
||||
}
|
||||
// Done with writing content, compute stats here
|
||||
if cdre.firstCdrATime.IsZero() || cdr.AnswerTime.Before(cdre.firstCdrATime) {
|
||||
cdre.firstCdrATime = cdr.AnswerTime
|
||||
}
|
||||
if cdr.AnswerTime.After(cdre.lastCdrATime) {
|
||||
cdre.lastCdrATime = cdr.AnswerTime
|
||||
}
|
||||
cdre.numberOfRecords += 1
|
||||
if cdr.TOR == utils.VOICE { // Only count duration for non data cdrs
|
||||
cdre.totalDuration += cdr.Usage
|
||||
}
|
||||
if cdr.TOR == utils.SMS { // Count usage for SMS
|
||||
cdre.totalSmsUsage += cdr.Usage
|
||||
}
|
||||
if cdr.TOR == utils.DATA { // Count usage for SMS
|
||||
cdre.totalDataUsage += cdr.Usage
|
||||
}
|
||||
if cdr.Cost != -1 {
|
||||
cdre.totalCost += cdr.Cost
|
||||
cdre.totalCost = utils.Round(cdre.totalCost, cdre.roundDecimals, utils.ROUNDING_MIDDLE)
|
||||
}
|
||||
if cdre.firstExpOrderId > cdr.OrderId || cdre.firstExpOrderId == 0 {
|
||||
cdre.firstExpOrderId = cdr.OrderId
|
||||
}
|
||||
if cdre.lastExpOrderId < cdr.OrderId {
|
||||
cdre.lastExpOrderId = cdr.OrderId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Builds header, content and trailers
|
||||
func (cdre *CdrExporter) processCdrs() error {
|
||||
for _, cdr := range cdre.cdrs {
|
||||
if err := cdre.processCdr(cdr); err != nil {
|
||||
cdre.negativeExports[cdr.CgrId] = err.Error()
|
||||
} else {
|
||||
cdre.positiveExports = append(cdre.positiveExports, cdr.CgrId)
|
||||
}
|
||||
}
|
||||
// Process header and trailer after processing cdrs since the metatag functions can access stats out of built cdrs
|
||||
if cdre.exportTemplate.HeaderFields != nil {
|
||||
if err := cdre.composeHeader(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cdre.exportTemplate.TrailerFields != nil {
|
||||
if err := cdre.composeTrailer(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Simple write method
|
||||
func (cdre *CdrExporter) writeOut(ioWriter io.Writer) error {
|
||||
if len(cdre.header) != 0 {
|
||||
for _, fld := range append(cdre.header, "\n") {
|
||||
if _, err := io.WriteString(ioWriter, fld); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cdrContent := range cdre.content {
|
||||
for _, cdrFld := range append(cdrContent, "\n") {
|
||||
if _, err := io.WriteString(ioWriter, cdrFld); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cdre.trailer) != 0 {
|
||||
for _, fld := range append(cdre.trailer, "\n") {
|
||||
if _, err := io.WriteString(ioWriter, fld); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// csvWriter specific method
|
||||
func (cdre *CdrExporter) writeCsv(csvWriter *csv.Writer) error {
|
||||
csvWriter.Comma = cdre.fieldSeparator
|
||||
if len(cdre.header) != 0 {
|
||||
if err := csvWriter.Write(cdre.header); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, cdrContent := range cdre.content {
|
||||
if err := csvWriter.Write(cdrContent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(cdre.trailer) != 0 {
|
||||
if err := csvWriter.Write(cdre.trailer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
csvWriter.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// General method to write the content out to a file
|
||||
func (cdre *CdrExporter) WriteToFile(filePath string) error {
|
||||
fileOut, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileOut.Close()
|
||||
switch cdre.cdrFormat {
|
||||
case utils.CDRE_DRYRUN:
|
||||
return nil
|
||||
case utils.CDRE_FIXED_WIDTH:
|
||||
if err := cdre.writeOut(fileOut); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
case utils.CSV:
|
||||
csvWriter := csv.NewWriter(fileOut)
|
||||
if err := cdre.writeCsv(csvWriter); err != nil {
|
||||
return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the first exported Cdr OrderId
|
||||
func (cdre *CdrExporter) FirstOrderId() int64 {
|
||||
return cdre.firstExpOrderId
|
||||
}
|
||||
|
||||
// Return the last exported Cdr OrderId
|
||||
func (cdre *CdrExporter) LastOrderId() int64 {
|
||||
return cdre.lastExpOrderId
|
||||
}
|
||||
|
||||
// Return total cost in the exported cdrs
|
||||
func (cdre *CdrExporter) TotalCost() float64 {
|
||||
return cdre.totalCost
|
||||
}
|
||||
|
||||
func (cdre *CdrExporter) TotalExportedCdrs() int {
|
||||
return cdre.numberOfRecords
|
||||
}
|
||||
|
||||
// Return successfully exported CgrIds
|
||||
func (cdre *CdrExporter) PositiveExports() []string {
|
||||
return cdre.positiveExports
|
||||
}
|
||||
|
||||
// Return failed exported CgrIds together with the reason
|
||||
func (cdre *CdrExporter) NegativeExports() map[string]string {
|
||||
return cdre.negativeExports
|
||||
}
|
||||
|
||||
113
cdre/cdrexporter_test.go
Normal file
113
cdre/cdrexporter_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package cdre
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCdreGetCombimedCdrFieldVal(t *testing.T) {
|
||||
logDb, _ := engine.NewMapStorage()
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
cdrs := []*utils.StoredCdr{
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: "RUN_RTL", Cost: 1.01},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf2", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: "CUSTOMER1", Cost: 2.01},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: "CUSTOMER1", Cost: 3.01},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 4.01},
|
||||
&utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1000", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: "RETAIL1", Cost: 5.01},
|
||||
}
|
||||
|
||||
cdre, err := NewCdrExporter(cdrs, logDb, cfg.CdreDefaultInstance, cfg.CdreDefaultInstance.CdrFormat, cfg.CdreDefaultInstance.FieldSeparator,
|
||||
"firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received: ", err)
|
||||
}
|
||||
fltrRule, _ := utils.NewRSRField("~mediation_runid:s/default/RUN_RTL/")
|
||||
if costVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], fltrRule, &utils.RSRField{Id: "cost"}); err != nil {
|
||||
t.Error(err)
|
||||
} else if costVal != "1.01" {
|
||||
t.Error("Expecting: 1.01, received: ", costVal)
|
||||
}
|
||||
fltrRule, _ = utils.NewRSRField("~mediation_runid:s/default/RETAIL1/")
|
||||
if acntVal, err := cdre.getCombimedCdrFieldVal(cdrs[3], fltrRule, &utils.RSRField{Id: "account"}); err != nil {
|
||||
t.Error(err)
|
||||
} else if acntVal != "1000" {
|
||||
t.Error("Expecting: 1000, received: ", acntVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDateTimeFieldVal(t *testing.T) {
|
||||
cdreTst := new(CdrExporter)
|
||||
cdrTst := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.01,
|
||||
ExtraFields: map[string]string{"stop_time": "2014-06-11 19:19:00 +0000 UTC", "fieldextr2": "valextr2"}}
|
||||
if cdrVal, err := cdreTst.getDateTimeFieldVal(cdrTst, nil, &utils.RSRField{Id: "stop_time"}, "2006-01-02 15:04:05"); err != nil {
|
||||
t.Error(err)
|
||||
} else if cdrVal != "2014-06-11 19:19:00" {
|
||||
t.Error("Expecting: 2014-06-11 19:19:00, got: ", cdrVal)
|
||||
}
|
||||
// Test filter
|
||||
fltrRule, _ := utils.NewRSRField("~tenant:s/(.+)/itsyscom.com/")
|
||||
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, fltrRule, &utils.RSRField{Id: "stop_time"}, "2006-01-02 15:04:05"); err == nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Test time parse error
|
||||
if _, err := cdreTst.getDateTimeFieldVal(cdrTst, nil, &utils.RSRField{Id: "fieldextr2"}, "2006-01-02 15:04:05"); err == nil {
|
||||
t.Error("Should give error here, got none.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdreCdrFieldValue(t *testing.T) {
|
||||
cdre := new(CdrExporter)
|
||||
cdr := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.01}
|
||||
fltrRule, _ := utils.NewRSRField("~tenant:s/(.+)/cgrates.org/")
|
||||
if val, err := cdre.cdrFieldValue(cdr, fltrRule, &utils.RSRField{Id: "destination"}, ""); err != nil {
|
||||
t.Error(err)
|
||||
} else if val != cdr.Destination {
|
||||
t.Errorf("Expecting: %s, received: %s", cdr.Destination, val)
|
||||
}
|
||||
fltrRule, _ = utils.NewRSRField("~tenant:s/(.+)/itsyscom.com/")
|
||||
if _, err := cdre.cdrFieldValue(cdr, fltrRule, &utils.RSRField{Id: "destination"}, ""); err == nil {
|
||||
t.Error("Failed to use filter")
|
||||
}
|
||||
}
|
||||
53
cdre/csv.go
53
cdre/csv.go
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package cdre
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
type CsvCdrWriter struct {
|
||||
writer *csv.Writer
|
||||
roundDecimals int // Round floats like Cost using this number of decimals
|
||||
exportedFields []*utils.RSRField // The fields exported, order important
|
||||
}
|
||||
|
||||
func NewCsvCdrWriter(writer io.Writer, roundDecimals int, exportedFields []*utils.RSRField) *CsvCdrWriter {
|
||||
return &CsvCdrWriter{csv.NewWriter(writer), roundDecimals, exportedFields}
|
||||
}
|
||||
|
||||
func (csvwr *CsvCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
row := make([]string, len(csvwr.exportedFields))
|
||||
for idx, fld := range csvwr.exportedFields {
|
||||
var fldVal string
|
||||
if fld.Id == utils.COST {
|
||||
fldVal = cdr.FormatCost(csvwr.roundDecimals)
|
||||
} else {
|
||||
fldVal = cdr.ExportFieldValue(fld.Id)
|
||||
}
|
||||
row[idx] = fld.ParseValue(fldVal)
|
||||
}
|
||||
return csvwr.writer.Write(row)
|
||||
}
|
||||
|
||||
func (csvwr *CsvCdrWriter) Close() {
|
||||
csvwr.writer.Flush()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,9 @@ package cdre
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -30,18 +32,55 @@ import (
|
||||
func TestCsvCdrWriter(t *testing.T) {
|
||||
writer := &bytes.Buffer{}
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
exportedFields := append(cfg.CdreExportedFields, &utils.RSRField{Id: "extra3"}, &utils.RSRField{Id: "dummy_extra"}, &utils.RSRField{Id: "extra1"})
|
||||
csvCdrWriter := NewCsvCdrWriter(writer, 4, exportedFields)
|
||||
ratedCdr := &utils.StoredCdr{CgrId: utils.FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
TOR: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
|
||||
logDb, _ := engine.NewMapStorage()
|
||||
storedCdr1 := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
|
||||
ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01,
|
||||
}
|
||||
csvCdrWriter.WriteCdr(ratedCdr)
|
||||
csvCdrWriter.Close()
|
||||
expected := `b18944ef4dc618569f24c27b9872827a242bad0c,default,dsafdsaf,192.168.1.1,rated,*out,cgrates.org,call,1001,1001,1002,2013-11-07 08:42:25 +0000 UTC,2013-11-07 08:42:26 +0000 UTC,10,1.0100,val_extra3,"",val_extra1`
|
||||
cdre, err := NewCdrExporter([]*utils.StoredCdr{storedCdr1}, logDb, cfg.CdreDefaultInstance, utils.CSV, ',', "firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received: ", err)
|
||||
}
|
||||
csvWriter := csv.NewWriter(writer)
|
||||
if err := cdre.writeCsv(csvWriter); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
}
|
||||
expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6,default,*voice,dsafdsaf,rated,*out,cgrates.org,call,1001,1001,1002,2013-11-07T08:42:25Z,2013-11-07T08:42:26Z,10000000000,1.0100`
|
||||
result := strings.TrimSpace(writer.String())
|
||||
if result != expected {
|
||||
t.Errorf("Expected: \n%s received: \n%s.", expected, result)
|
||||
}
|
||||
if cdre.TotalCost() != 1.01 {
|
||||
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternativeFieldSeparator(t *testing.T) {
|
||||
writer := &bytes.Buffer{}
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
logDb, _ := engine.NewMapStorage()
|
||||
storedCdr1 := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()), TOR: utils.VOICE, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002", SetupTime: time.Unix(1383813745, 0).UTC(), AnswerTime: time.Unix(1383813746, 0).UTC(),
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID,
|
||||
ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01,
|
||||
}
|
||||
cdre, err := NewCdrExporter([]*utils.StoredCdr{storedCdr1}, logDb, cfg.CdreDefaultInstance, utils.CSV, '|', "firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify)
|
||||
if err != nil {
|
||||
t.Error("Unexpected error received: ", err)
|
||||
}
|
||||
csvWriter := csv.NewWriter(writer)
|
||||
if err := cdre.writeCsv(csvWriter); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
}
|
||||
expected := `dbafe9c8614c785a65aabd116dd3959c3c56f7f6|default|*voice|dsafdsaf|rated|*out|cgrates.org|call|1001|1001|1002|2013-11-07T08:42:25Z|2013-11-07T08:42:26Z|10000000000|1.0100`
|
||||
result := strings.TrimSpace(writer.String())
|
||||
if result != expected {
|
||||
t.Errorf("Expected: \n%s received: \n%s.", expected, result)
|
||||
}
|
||||
if cdre.TotalCost() != 1.01 {
|
||||
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package cdre
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
COST_DETAILS = "cost_details"
|
||||
FILLER = "filler"
|
||||
CONSTANT = "constant"
|
||||
CDRFIELD = "cdrfield"
|
||||
METATAG = "metatag"
|
||||
CONCATENATED_CDRFIELD = "concatenated_cdrfield"
|
||||
META_EXPORTID = "export_id"
|
||||
META_TIMENOW = "time_now"
|
||||
META_FIRSTCDRTIME = "first_cdr_time"
|
||||
META_LASTCDRTIME = "last_cdr_time"
|
||||
META_NRCDRS = "cdrs_number"
|
||||
META_DURCDRS = "cdrs_duration"
|
||||
META_COSTCDRS = "cdrs_cost"
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
func NewFWCdrWriter(logDb engine.LogStorage, outFile *os.File, exportTpl *config.CgrXmlCdreFwCfg, exportId string, roundDecimals int) (*FixedWidthCdrWriter, error) {
|
||||
return &FixedWidthCdrWriter{
|
||||
logDb: logDb,
|
||||
writer: outFile,
|
||||
exportTemplate: exportTpl,
|
||||
exportId: exportId,
|
||||
roundDecimals: roundDecimals,
|
||||
header: &bytes.Buffer{},
|
||||
content: &bytes.Buffer{},
|
||||
trailer: &bytes.Buffer{}}, nil
|
||||
}
|
||||
|
||||
type FixedWidthCdrWriter struct {
|
||||
logDb engine.LogStorage // Used to extract cost_details if these are requested
|
||||
writer io.Writer
|
||||
exportTemplate *config.CgrXmlCdreFwCfg
|
||||
exportId string // Unique identifier or this export
|
||||
roundDecimals int
|
||||
header, content, trailer *bytes.Buffer
|
||||
firstCdrTime, lastCdrTime time.Time
|
||||
numberOfRecords int
|
||||
totalDuration time.Duration
|
||||
totalCost float64
|
||||
}
|
||||
|
||||
// Return Json marshaled callCost attached to
|
||||
// Keep it separately so we test only this part in local tests
|
||||
func (fww *FixedWidthCdrWriter) getCdrCostDetails(cgrId, runId string) (string, error) {
|
||||
cc, err := fww.logDb.GetCallCostLog(cgrId, "", runId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if cc == nil {
|
||||
return "", nil
|
||||
}
|
||||
ccJson, _ := json.Marshal(cc)
|
||||
return string(ccJson), nil
|
||||
}
|
||||
|
||||
// Extracts the value specified by cfgHdr out of cdr
|
||||
func (fww *FixedWidthCdrWriter) cdrFieldValue(cdr *utils.StoredCdr, cfgHdr, layout string) (string, error) {
|
||||
rsrField, err := utils.NewRSRField(cfgHdr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if rsrField == nil {
|
||||
return "", nil
|
||||
}
|
||||
var cdrVal string
|
||||
switch rsrField.Id {
|
||||
case COST_DETAILS: // Special case when we need to further extract cost_details out of logDb
|
||||
if cdrVal, err = fww.getCdrCostDetails(cdr.CgrId, cdr.MediationRunId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case utils.COST:
|
||||
cdrVal = cdr.FormatCost(fww.roundDecimals)
|
||||
case utils.SETUP_TIME:
|
||||
cdrVal = cdr.SetupTime.Format(layout)
|
||||
case utils.ANSWER_TIME: // Format time based on layout
|
||||
cdrVal = cdr.AnswerTime.Format(layout)
|
||||
default:
|
||||
cdrVal = cdr.ExportFieldValue(rsrField.Id)
|
||||
}
|
||||
return rsrField.ParseValue(cdrVal), nil
|
||||
}
|
||||
|
||||
func (fww *FixedWidthCdrWriter) metaHandler(tag, layout string) (string, error) {
|
||||
switch tag {
|
||||
case META_EXPORTID:
|
||||
return fww.exportId, nil
|
||||
case META_TIMENOW:
|
||||
return time.Now().Format(layout), nil
|
||||
case META_FIRSTCDRTIME:
|
||||
return fww.firstCdrTime.Format(layout), nil
|
||||
case META_LASTCDRTIME:
|
||||
return fww.lastCdrTime.Format(layout), nil
|
||||
case META_NRCDRS:
|
||||
return strconv.Itoa(fww.numberOfRecords), nil
|
||||
case META_DURCDRS:
|
||||
return strconv.FormatFloat(fww.totalDuration.Seconds(), 'f', -1, 64), nil
|
||||
case META_COSTCDRS:
|
||||
return strconv.FormatFloat(utils.Round(fww.totalCost, fww.roundDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil
|
||||
default:
|
||||
return "", errors.New("Unsupported METATAG")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Writes the header into it's buffer
|
||||
func (fww *FixedWidthCdrWriter) ComposeHeader() error {
|
||||
header := ""
|
||||
for _, cfgFld := range fww.exportTemplate.Header.Fields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER, CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case METATAG:
|
||||
outVal, err = fww.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, error: %s", err.Error()))
|
||||
return err
|
||||
}
|
||||
if fmtOut, err := FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR header, error: %s", err.Error()))
|
||||
return err
|
||||
} else {
|
||||
header += fmtOut
|
||||
}
|
||||
}
|
||||
if len(header) == 0 { // No header data, most likely no configuration fields defined
|
||||
return nil
|
||||
}
|
||||
header += "\n" // Done with cdr, postpend new line char
|
||||
fww.header.WriteString(header)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Writes the trailer into it's buffer
|
||||
func (fww *FixedWidthCdrWriter) ComposeTrailer() error {
|
||||
trailer := ""
|
||||
for _, cfgFld := range fww.exportTemplate.Trailer.Fields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER, CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case METATAG:
|
||||
outVal, err = fww.metaHandler(cfgFld.Value, cfgFld.Layout)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported field type: %s", cfgFld.Type)
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, error: %s", err.Error()))
|
||||
return err
|
||||
}
|
||||
if fmtOut, err := FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR trailer, error: %s", err.Error()))
|
||||
return err
|
||||
} else {
|
||||
trailer += fmtOut
|
||||
}
|
||||
}
|
||||
if len(trailer) == 0 { // No header data, most likely no configuration fields defined
|
||||
return nil
|
||||
}
|
||||
trailer += "\n" // Done with cdr, postpend new line char
|
||||
fww.trailer.WriteString(trailer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write individual cdr into content buffer, build stats
|
||||
func (fww *FixedWidthCdrWriter) WriteCdr(cdr *utils.StoredCdr) error {
|
||||
if cdr == nil || len(cdr.CgrId) == 0 { // We do not export empty CDRs
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
cdrRow := ""
|
||||
for _, cfgFld := range fww.exportTemplate.Content.Fields {
|
||||
var outVal string
|
||||
switch cfgFld.Type {
|
||||
case FILLER, CONSTANT:
|
||||
outVal = cfgFld.Value
|
||||
case CDRFIELD:
|
||||
outVal, err = fww.cdrFieldValue(cdr, cfgFld.Value, cfgFld.Layout)
|
||||
case CONCATENATED_CDRFIELD:
|
||||
for _, fld := range strings.Split(cfgFld.Value, ",") {
|
||||
if fldOut, err := fww.cdrFieldValue(cdr, fld, cfgFld.Layout); err != nil {
|
||||
break // The error will be reported bellow
|
||||
} else {
|
||||
outVal += fldOut
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
|
||||
return err
|
||||
}
|
||||
if fmtOut, err := FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<CdreFw> Cannot export CDR with cgrid: %s and runid: %s, error: %s", cdr.CgrId, cdr.MediationRunId, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
cdrRow += fmtOut
|
||||
}
|
||||
}
|
||||
if len(cdrRow) == 0 { // No CDR data, most likely no configuration fields defined
|
||||
return nil
|
||||
}
|
||||
cdrRow += "\n" // Done with cdr, postpend new line char
|
||||
fww.content.WriteString(cdrRow)
|
||||
// Done with writing content, compute stats here
|
||||
if fww.firstCdrTime.IsZero() || cdr.SetupTime.Before(fww.firstCdrTime) {
|
||||
fww.firstCdrTime = cdr.SetupTime
|
||||
}
|
||||
if cdr.SetupTime.After(fww.lastCdrTime) {
|
||||
fww.lastCdrTime = cdr.SetupTime
|
||||
}
|
||||
fww.numberOfRecords += 1
|
||||
fww.totalDuration += cdr.Duration
|
||||
fww.totalCost += cdr.Cost
|
||||
fww.totalCost = utils.Round(fww.totalCost, fww.roundDecimals, utils.ROUNDING_MIDDLE)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fww *FixedWidthCdrWriter) Close() {
|
||||
if fww.exportTemplate.Header != nil {
|
||||
fww.ComposeHeader()
|
||||
}
|
||||
if fww.exportTemplate.Trailer != nil {
|
||||
fww.ComposeTrailer()
|
||||
}
|
||||
for _, buf := range []*bytes.Buffer{fww.header, fww.content, fww.trailer} {
|
||||
fww.writer.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package cdre
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"math"
|
||||
"testing"
|
||||
@@ -29,82 +30,81 @@ import (
|
||||
|
||||
var hdrCfgFlds = []*config.CgrXmlCfgCdrField{
|
||||
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "10", Width: 2},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3},
|
||||
&config.CgrXmlCfgCdrField{Name: "DistributorCode", Type: CONSTANT, Value: "VOI", Width: 3},
|
||||
&config.CgrXmlCfgCdrField{Name: "FileSeqNr", Type: METATAG, Value: "export_id", Width: 5, Strip: "right", Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "LastCdr", Type: METATAG, Value: "last_cdr_time", Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "LastCdr", Type: METATAG, Value: META_LASTCDRATIME, Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "FileCreationfTime", Type: METATAG, Value: "time_now", Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "FileVersion", Type: CONSTANT, Value: "01", Width: 2},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 105, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 105},
|
||||
}
|
||||
|
||||
var contentCfgFlds = []*config.CgrXmlCfgCdrField{
|
||||
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "20", Width: 2},
|
||||
&config.CgrXmlCfgCdrField{Name: "Account", Type: CDRFIELD, Value: utils.ACCOUNT, Width: 12, Strip: "left", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Subject", Type: CDRFIELD, Value: utils.SUBJECT, Width: 5, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "CLI", Type: CDRFIELD, Value: "cli", Width: 15, Strip: "xright", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Destination", Type: CDRFIELD, Value: utils.DESTINATION, Width: 24, Strip: "xright", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Account", Type: utils.CDRFIELD, Value: utils.ACCOUNT, Width: 12, Strip: "left", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Subject", Type: utils.CDRFIELD, Value: utils.SUBJECT, Width: 5, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "CLI", Type: utils.CDRFIELD, Value: "cli", Width: 15, Strip: "xright", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Destination", Type: utils.CDRFIELD, Value: utils.DESTINATION, Width: 24, Strip: "xright", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "TOR", Type: CONSTANT, Value: "02", Width: 2},
|
||||
&config.CgrXmlCfgCdrField{Name: "SubtypeTOR", Type: CONSTANT, Value: "11", Width: 4, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "SetupTime", Type: CDRFIELD, Value: utils.SETUP_TIME, Width: 12, Strip: "right", Padding: "right", Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Duration", Type: CDRFIELD, Value: utils.DURATION, Width: 6, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "DataVolume", Type: FILLER, Width: 6, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "SetupTime", Type: utils.CDRFIELD, Value: utils.SETUP_TIME, Width: 12, Strip: "right", Padding: "right", Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Duration", Type: utils.CDRFIELD, Value: utils.USAGE, Width: 6, Strip: "right", Padding: "right", Layout: utils.SECONDS},
|
||||
&config.CgrXmlCfgCdrField{Name: "DataVolume", Type: FILLER, Width: 6},
|
||||
&config.CgrXmlCfgCdrField{Name: "TaxCode", Type: CONSTANT, Value: "1", Width: 1},
|
||||
&config.CgrXmlCfgCdrField{Name: "OperatorCode", Type: CDRFIELD, Value: "opercode", Width: 2, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "ProductId", Type: CDRFIELD, Value: "productid", Width: 5, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "OperatorCode", Type: utils.CDRFIELD, Value: "opercode", Width: 2, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "ProductId", Type: utils.CDRFIELD, Value: "productid", Width: 5, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "NetworkId", Type: CONSTANT, Value: "3", Width: 1},
|
||||
&config.CgrXmlCfgCdrField{Name: "CallId", Type: CDRFIELD, Value: utils.ACCID, Width: 16, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "CallId", Type: utils.CDRFIELD, Value: utils.ACCID, Width: 16, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler", Type: FILLER, Width: 8},
|
||||
&config.CgrXmlCfgCdrField{Name: "TerminationCode", Type: CONCATENATED_CDRFIELD, Value: "operator,product", Width: 5, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Cost", Type: CDRFIELD, Value: utils.COST, Width: 9, Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: CDRFIELD, Value: "destination_privacy", Width: 1, Strip: "right", Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Cost", Type: utils.CDRFIELD, Value: utils.COST, Width: 9, Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "DestinationPrivacy", Type: METATAG, Value: META_MASKDESTINATION, Width: 1},
|
||||
}
|
||||
|
||||
var trailerCfgFlds = []*config.CgrXmlCfgCdrField{
|
||||
&config.CgrXmlCfgCdrField{Name: "TypeOfRecord", Type: CONSTANT, Value: "90", Width: 2},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler1", Type: FILLER, Width: 3},
|
||||
&config.CgrXmlCfgCdrField{Name: "DistributorCode", Type: CONSTANT, Value: "VOI", Width: 3},
|
||||
&config.CgrXmlCfgCdrField{Name: "FileSeqNr", Type: METATAG, Value: META_EXPORTID, Width: 5, Strip: "right", Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "NumberOfRecords", Type: METATAG, Value: META_NRCDRS, Width: 6, Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "CdrsDuration", Type: METATAG, Value: META_DURCDRS, Width: 8, Padding: "zeroleft"},
|
||||
&config.CgrXmlCfgCdrField{Name: "FirstCdrTime", Type: METATAG, Value: META_FIRSTCDRTIME, Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "LastCdrTime", Type: METATAG, Value: META_LASTCDRTIME, Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 93, Padding: "right"},
|
||||
&config.CgrXmlCfgCdrField{Name: "CdrsDuration", Type: METATAG, Value: META_DURCDRS, Width: 8, Padding: "zeroleft", Layout: "seconds"},
|
||||
&config.CgrXmlCfgCdrField{Name: "FirstCdrTime", Type: METATAG, Value: META_FIRSTCDRATIME, Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "LastCdrTime", Type: METATAG, Value: META_LASTCDRATIME, Width: 12, Layout: "020106150400"},
|
||||
&config.CgrXmlCfgCdrField{Name: "Filler2", Type: FILLER, Width: 93},
|
||||
}
|
||||
|
||||
// Write one CDR and test it's results only for content buffer
|
||||
func TestWriteCdr(t *testing.T) {
|
||||
wrBuf := &bytes.Buffer{}
|
||||
exportTpl := &config.CgrXmlCdreFwCfg{Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
|
||||
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
|
||||
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
|
||||
logDb, _ := engine.NewMapStorage()
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
fixedWidth := utils.CDRE_FIXED_WIDTH
|
||||
exportTpl := &config.CgrXmlCdreCfg{
|
||||
CdrFormat: &fixedWidth,
|
||||
Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
|
||||
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
|
||||
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
|
||||
}
|
||||
fwWriter := FixedWidthCdrWriter{writer: wrBuf, exportTemplate: exportTpl, roundDecimals: 4, header: &bytes.Buffer{}, content: &bytes.Buffer{}, trailer: &bytes.Buffer{}}
|
||||
cdr := &utils.StoredCdr{CgrId: utils.FSCgrId("dsafdsaf"), AccId: "dsafdsaf", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
TOR: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
cdr := &utils.StoredCdr{CgrId: utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE, OrderId: 1, AccId: "dsafdsaf", CdrHost: "192.168.1.1",
|
||||
ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1002",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
|
||||
ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"},
|
||||
}
|
||||
if err := fwWriter.WriteCdr(cdr); err != nil {
|
||||
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',', "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.3457 \n"
|
||||
contentOut := fwWriter.content.String()
|
||||
if len(contentOut) != 145 {
|
||||
t.Error("Unexpected content length", len(contentOut))
|
||||
} else if contentOut != eContentOut {
|
||||
t.Errorf("Content out different than expected. Have <%s>, expecting: <%s>", contentOut, eContentOut)
|
||||
}
|
||||
eHeader := "10 VOI0000007111308420024031415390001 \n"
|
||||
eTrailer := "90 VOI0000000000100000010071113084200071113084200 \n"
|
||||
outBeforeWrite := ""
|
||||
if wrBuf.String() != outBeforeWrite {
|
||||
t.Errorf("Output buffer should be empty before write")
|
||||
eContentOut := "201001 1001 1002 0211 07111308420010 1 3dsafdsaf 0002.34570\n"
|
||||
eTrailer := "90 VOI0000000000100000010071113084260071113084200 \n"
|
||||
if err := cdre.writeOut(wrBuf); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
fwWriter.Close()
|
||||
allOut := wrBuf.String()
|
||||
eAllOut := eHeader + eContentOut + eTrailer
|
||||
if math.Mod(float64(len(allOut)), 145) != 0 {
|
||||
@@ -113,78 +113,98 @@ func TestWriteCdr(t *testing.T) {
|
||||
t.Errorf("Output does not match expected length. Have output %q, expecting: %q", allOut, eAllOut)
|
||||
}
|
||||
// Test stats
|
||||
if !fwWriter.firstCdrTime.Equal(cdr.SetupTime) {
|
||||
t.Error("Unexpected firstCdrTime in stats: ", fwWriter.firstCdrTime)
|
||||
} else if !fwWriter.lastCdrTime.Equal(cdr.SetupTime) {
|
||||
t.Error("Unexpected lastCdrTime in stats: ", fwWriter.lastCdrTime)
|
||||
} else if fwWriter.numberOfRecords != 1 {
|
||||
t.Error("Unexpected number of records in the stats: ", fwWriter.numberOfRecords)
|
||||
} else if fwWriter.totalDuration != cdr.Duration {
|
||||
t.Error("Unexpected total duration in the stats: ", fwWriter.totalDuration)
|
||||
} else if fwWriter.totalCost != utils.Round(cdr.Cost, fwWriter.roundDecimals, utils.ROUNDING_MIDDLE) {
|
||||
t.Error("Unexpected total cost in the stats: ", fwWriter.totalCost)
|
||||
if !cdre.firstCdrATime.Equal(cdr.AnswerTime) {
|
||||
t.Error("Unexpected firstCdrATime in stats: ", cdre.firstCdrATime)
|
||||
} else if !cdre.lastCdrATime.Equal(cdr.AnswerTime) {
|
||||
t.Error("Unexpected lastCdrATime in stats: ", cdre.lastCdrATime)
|
||||
} else if cdre.numberOfRecords != 1 {
|
||||
t.Error("Unexpected number of records in the stats: ", cdre.numberOfRecords)
|
||||
} else if cdre.totalDuration != cdr.Usage {
|
||||
t.Error("Unexpected total duration in the stats: ", cdre.totalDuration)
|
||||
} else if cdre.totalCost != utils.Round(cdr.Cost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) {
|
||||
t.Error("Unexpected total cost in the stats: ", cdre.totalCost)
|
||||
}
|
||||
if cdre.FirstOrderId() != 1 {
|
||||
t.Error("Unexpected FirstOrderId", cdre.FirstOrderId())
|
||||
}
|
||||
if cdre.LastOrderId() != 1 {
|
||||
t.Error("Unexpected LastOrderId", cdre.LastOrderId())
|
||||
}
|
||||
if cdre.TotalCost() != utils.Round(cdr.Cost, cdre.roundDecimals, utils.ROUNDING_MIDDLE) {
|
||||
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCdrs(t *testing.T) {
|
||||
wrBuf := &bytes.Buffer{}
|
||||
exportTpl := &config.CgrXmlCdreFwCfg{Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
|
||||
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
|
||||
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
|
||||
logDb, _ := engine.NewMapStorage()
|
||||
fixedWidth := utils.CDRE_FIXED_WIDTH
|
||||
exportTpl := &config.CgrXmlCdreCfg{
|
||||
CdrFormat: &fixedWidth,
|
||||
Header: &config.CgrXmlCfgCdrHeader{Fields: hdrCfgFlds},
|
||||
Content: &config.CgrXmlCfgCdrContent{Fields: contentCfgFlds},
|
||||
Trailer: &config.CgrXmlCfgCdrTrailer{Fields: trailerCfgFlds},
|
||||
}
|
||||
fwWriter := FixedWidthCdrWriter{writer: wrBuf, exportTemplate: exportTpl, roundDecimals: 4, header: &bytes.Buffer{}, content: &bytes.Buffer{}, trailer: &bytes.Buffer{}}
|
||||
cdr1 := &utils.StoredCdr{CgrId: utils.FSCgrId("aaa1"), AccId: "aaa1", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
TOR: "call", Account: "1001", Subject: "1001", Destination: "1010",
|
||||
cdr1 := &utils.StoredCdr{CgrId: utils.Sha1("aaa1", time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE, OrderId: 2, AccId: "aaa1", CdrHost: "192.168.1.1", ReqType: "rated", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1001", Subject: "1001", Destination: "1010",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.25,
|
||||
Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.25,
|
||||
ExtraFields: map[string]string{"productnumber": "12341", "fieldextr2": "valextr2"},
|
||||
}
|
||||
cdr2 := &utils.StoredCdr{CgrId: utils.FSCgrId("aaa2"), AccId: "aaa2", CdrHost: "192.168.1.2", ReqType: "prepaid", Direction: "*out", Tenant: "cgrates.org",
|
||||
TOR: "call", Account: "1002", Subject: "1002", Destination: "1011",
|
||||
cdr2 := &utils.StoredCdr{CgrId: utils.Sha1("aaa2", time.Date(2013, 11, 7, 7, 42, 20, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE, OrderId: 4, AccId: "aaa2", CdrHost: "192.168.1.2", ReqType: "prepaid", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1002", Subject: "1002", Destination: "1011",
|
||||
SetupTime: time.Date(2013, 11, 7, 7, 42, 20, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 11, 7, 7, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(5) * time.Minute, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.40001,
|
||||
Usage: time.Duration(5) * time.Minute, MediationRunId: utils.DEFAULT_RUNID, Cost: 1.40001,
|
||||
ExtraFields: map[string]string{"productnumber": "12342", "fieldextr2": "valextr2"},
|
||||
}
|
||||
cdr3 := &utils.StoredCdr{}
|
||||
cdr4 := &utils.StoredCdr{CgrId: utils.FSCgrId("aaa3"), AccId: "aaa4", CdrHost: "192.168.1.4", ReqType: "postpaid", Direction: "*out", Tenant: "cgrates.org",
|
||||
TOR: "call", Account: "1004", Subject: "1004", Destination: "1013",
|
||||
cdr4 := &utils.StoredCdr{CgrId: utils.Sha1("aaa3", time.Date(2013, 11, 7, 9, 42, 18, 0, time.UTC).String()),
|
||||
TOR: utils.VOICE, OrderId: 3, AccId: "aaa4", CdrHost: "192.168.1.4", ReqType: "postpaid", Direction: "*out", Tenant: "cgrates.org",
|
||||
Category: "call", Account: "1004", Subject: "1004", Destination: "1013",
|
||||
SetupTime: time.Date(2013, 11, 7, 9, 42, 18, 0, time.UTC),
|
||||
AnswerTime: time.Date(2013, 11, 7, 9, 42, 26, 0, time.UTC),
|
||||
Duration: time.Duration(20) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
|
||||
Usage: time.Duration(20) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567,
|
||||
ExtraFields: map[string]string{"productnumber": "12344", "fieldextr2": "valextr2"},
|
||||
}
|
||||
for _, cdr := range []*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4} {
|
||||
if err := fwWriter.WriteCdr(cdr); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
contentOut := fwWriter.content.String()
|
||||
if math.Mod(float64(len(contentOut)), 145) != 0 { // Rest must be 0 always, so content is always multiple of 145 which is our row fixLength
|
||||
t.Error("Unexpected content length", len(contentOut))
|
||||
}
|
||||
cfg, _ := config.NewDefaultCGRConfig()
|
||||
cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4}, logDb, exportTpl.AsCdreConfig(), utils.CDRE_FIXED_WIDTH, ',',
|
||||
"fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(wrBuf.String()) != 0 {
|
||||
t.Errorf("Output buffer should be empty before write")
|
||||
if err := cdre.writeOut(wrBuf); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
fwWriter.Close()
|
||||
if len(wrBuf.String()) != 725 {
|
||||
t.Error("Output buffer does not contain expected info. Expecting len: 725, got: ", len(wrBuf.String()))
|
||||
}
|
||||
// Test stats
|
||||
if !fwWriter.firstCdrTime.Equal(cdr2.SetupTime) {
|
||||
t.Error("Unexpected firstCdrTime in stats: ", fwWriter.firstCdrTime)
|
||||
if !cdre.firstCdrATime.Equal(cdr2.AnswerTime) {
|
||||
t.Error("Unexpected firstCdrATime in stats: ", cdre.firstCdrATime)
|
||||
}
|
||||
if !fwWriter.lastCdrTime.Equal(cdr4.SetupTime) {
|
||||
t.Error("Unexpected lastCdrTime in stats: ", fwWriter.lastCdrTime)
|
||||
if !cdre.lastCdrATime.Equal(cdr4.AnswerTime) {
|
||||
t.Error("Unexpected lastCdrATime in stats: ", cdre.lastCdrATime)
|
||||
}
|
||||
if fwWriter.numberOfRecords != 3 {
|
||||
t.Error("Unexpected number of records in the stats: ", fwWriter.numberOfRecords)
|
||||
if cdre.numberOfRecords != 3 {
|
||||
t.Error("Unexpected number of records in the stats: ", cdre.numberOfRecords)
|
||||
}
|
||||
if fwWriter.totalDuration != time.Duration(330)*time.Second {
|
||||
t.Error("Unexpected total duration in the stats: ", fwWriter.totalDuration)
|
||||
if cdre.totalDuration != time.Duration(330)*time.Second {
|
||||
t.Error("Unexpected total duration in the stats: ", cdre.totalDuration)
|
||||
}
|
||||
if fwWriter.totalCost != 5.9957 {
|
||||
t.Error("Unexpected total cost in the stats: ", fwWriter.totalCost)
|
||||
if cdre.totalCost != 5.9957 {
|
||||
t.Error("Unexpected total cost in the stats: ", cdre.totalCost)
|
||||
}
|
||||
if cdre.FirstOrderId() != 2 {
|
||||
t.Error("Unexpected FirstOrderId", cdre.FirstOrderId())
|
||||
}
|
||||
if cdre.LastOrderId() != 4 {
|
||||
t.Error("Unexpected LastOrderId", cdre.LastOrderId())
|
||||
}
|
||||
if cdre.TotalCost() != 5.9957 {
|
||||
t.Error("Unexpected TotalCost: ", cdre.TotalCost())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package cdre
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Used as generic function logic for various fields
|
||||
@@ -34,6 +35,9 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo
|
||||
if mandatory && len(source) == 0 {
|
||||
return "", errors.New("Empty source value")
|
||||
}
|
||||
if width == 0 { // Disable width processing if not defined
|
||||
return source, nil
|
||||
}
|
||||
if len(source) == width { // the source is exactly the maximum length
|
||||
return source, nil
|
||||
}
|
||||
@@ -71,3 +75,18 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// Mask a number of characters in the suffix of the destination
|
||||
func MaskDestination(dest string, maskLen int) string {
|
||||
destLen := len(dest)
|
||||
if maskLen < 0 {
|
||||
return dest
|
||||
} else if maskLen > destLen {
|
||||
maskLen = destLen
|
||||
}
|
||||
dest = dest[:destLen-maskLen]
|
||||
for i := 0; i < maskLen; i++ {
|
||||
dest += utils.MASK_CHAR
|
||||
}
|
||||
return dest
|
||||
}
|
||||
@@ -114,3 +114,20 @@ func TestPaddingNotAllowed(t *testing.T) {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskDestination(t *testing.T) {
|
||||
dest := "+4986517174963"
|
||||
if destMasked := MaskDestination(dest, 3); destMasked != "+4986517174***" {
|
||||
t.Error("Unexpected mask applied", destMasked)
|
||||
}
|
||||
if destMasked := MaskDestination(dest, -1); destMasked != dest {
|
||||
t.Error("Negative maskLen should not modify destination", destMasked)
|
||||
}
|
||||
if destMasked := MaskDestination(dest, 0); destMasked != dest {
|
||||
t.Error("Zero maskLen should not modify destination", destMasked)
|
||||
}
|
||||
if destMasked := MaskDestination(dest, 100); destMasked != "**************" {
|
||||
t.Error("High maskLen should return complete mask", destMasked)
|
||||
}
|
||||
|
||||
}
|
||||
310
cdrs/fscdr.go
310
cdrs/fscdr.go
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package cdrs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// Freswitch event property names
|
||||
FS_CDR_MAP = "variables"
|
||||
FS_DIRECTION = "direction"
|
||||
FS_SUBJECT = "cgr_subject"
|
||||
FS_ACCOUNT = "cgr_account"
|
||||
FS_DESTINATION = "cgr_destination"
|
||||
FS_REQTYPE = "cgr_reqtype" //prepaid or postpaid
|
||||
FS_TOR = "cgr_tor"
|
||||
FS_UUID = "uuid" // -Unique ID for this call leg
|
||||
FS_CSTMID = "cgr_tenant"
|
||||
FS_CALL_DEST_NR = "dialed_extension"
|
||||
FS_PARK_TIME = "start_epoch"
|
||||
FS_SETUP_TIME = "start_epoch"
|
||||
FS_ANSWER_TIME = "answer_epoch"
|
||||
FS_HANGUP_TIME = "end_epoch"
|
||||
FS_DURATION = "billsec"
|
||||
FS_USERNAME = "user_name"
|
||||
FS_IP = "sip_local_network_addr"
|
||||
FS_CDR_SOURCE = "freeswitch_json"
|
||||
FS_SIP_REQUSER = "sip_req_user" // Apps like FusionPBX do not set dialed_extension, alternative being destination_number but that comes in customer profile, not in vars
|
||||
)
|
||||
|
||||
type FSCdr struct {
|
||||
vars map[string]string
|
||||
body map[string]interface{} // keeps the loaded body for extra field search
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) New(body []byte) (utils.RawCDR, error) {
|
||||
fsCdr.vars = make(map[string]string)
|
||||
var err error
|
||||
if err = json.Unmarshal(body, &fsCdr.body); err == nil {
|
||||
if variables, ok := fsCdr.body[FS_CDR_MAP]; ok {
|
||||
if variables, ok := variables.(map[string]interface{}); ok {
|
||||
for k, v := range variables {
|
||||
fsCdr.vars[k] = v.(string)
|
||||
}
|
||||
}
|
||||
return fsCdr, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetCgrId() string {
|
||||
return utils.FSCgrId(fsCdr.vars[FS_UUID])
|
||||
}
|
||||
func (fsCdr FSCdr) GetAccId() string {
|
||||
return fsCdr.vars[FS_UUID]
|
||||
}
|
||||
func (fsCdr FSCdr) GetCdrHost() string {
|
||||
return fsCdr.vars[FS_IP]
|
||||
}
|
||||
func (fsCdr FSCdr) GetCdrSource() string {
|
||||
return FS_CDR_SOURCE
|
||||
}
|
||||
func (fsCdr FSCdr) GetDirection() string {
|
||||
//TODO: implement direction, not related to FS_DIRECTION but traffic towards or from subject/account
|
||||
return "*out"
|
||||
}
|
||||
func (fsCdr FSCdr) GetSubject() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_SUBJECT], fsCdr.vars[FS_USERNAME])
|
||||
}
|
||||
func (fsCdr FSCdr) GetAccount() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_ACCOUNT], fsCdr.vars[FS_USERNAME])
|
||||
}
|
||||
|
||||
// Charging destination number
|
||||
func (fsCdr FSCdr) GetDestination() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_DESTINATION], fsCdr.vars[FS_CALL_DEST_NR], fsCdr.vars[FS_SIP_REQUSER])
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetTOR() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_TOR], cfg.DefaultTOR)
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetTenant() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_CSTMID], cfg.DefaultTenant)
|
||||
}
|
||||
func (fsCdr FSCdr) GetReqType() string {
|
||||
return utils.FirstNonEmpty(fsCdr.vars[FS_REQTYPE], cfg.DefaultReqType)
|
||||
}
|
||||
func (fsCdr FSCdr) GetExtraFields() map[string]string {
|
||||
extraFields := make(map[string]string, len(cfg.CDRSExtraFields))
|
||||
for _, field := range cfg.CDRSExtraFields {
|
||||
origFieldVal, foundInVars := fsCdr.vars[field.Id]
|
||||
if !foundInVars {
|
||||
origFieldVal = fsCdr.searchExtraField(field.Id, fsCdr.body)
|
||||
}
|
||||
extraFields[field.Id] = field.ParseValue(origFieldVal)
|
||||
}
|
||||
return extraFields
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) searchExtraField(field string, body map[string]interface{}) (result string) {
|
||||
for key, value := range body {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
if key == field {
|
||||
return v
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if result = fsCdr.searchExtraField(field, v); result != "" {
|
||||
return
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range v {
|
||||
if otherMap, ok := item.(map[string]interface{}); ok {
|
||||
if result = fsCdr.searchExtraField(field, otherMap); result != "" {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
engine.Logger.Warning(fmt.Sprintf("Slice with no maps: %v", reflect.TypeOf(item)))
|
||||
}
|
||||
}
|
||||
default:
|
||||
engine.Logger.Warning(fmt.Sprintf("Unexpected type: %v", reflect.TypeOf(v)))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) GetSetupTime() (t time.Time, err error) {
|
||||
//ToDo: Make sure we work with UTC instead of local time
|
||||
at, err := strconv.ParseInt(fsCdr.vars[FS_SETUP_TIME], 0, 64)
|
||||
t = time.Unix(at, 0)
|
||||
return
|
||||
}
|
||||
func (fsCdr FSCdr) GetAnswerTime() (t time.Time, err error) {
|
||||
//ToDo: Make sure we work with UTC instead of local time
|
||||
at, err := strconv.ParseInt(fsCdr.vars[FS_ANSWER_TIME], 0, 64)
|
||||
t = time.Unix(at, 0)
|
||||
return
|
||||
}
|
||||
func (fsCdr FSCdr) GetHangupTime() (t time.Time, err error) {
|
||||
hupt, err := strconv.ParseInt(fsCdr.vars[FS_HANGUP_TIME], 0, 64)
|
||||
t = time.Unix(hupt, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Extracts duration as considered by the telecom switch
|
||||
func (fsCdr FSCdr) GetDuration() time.Duration {
|
||||
dur, _ := utils.ParseDurationWithSecs(fsCdr.vars[FS_DURATION])
|
||||
return dur
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) Store() (result string, err error) {
|
||||
result += fsCdr.GetCgrId() + "|"
|
||||
result += fsCdr.GetAccId() + "|"
|
||||
result += fsCdr.GetCdrHost() + "|"
|
||||
result += fsCdr.GetDirection() + "|"
|
||||
result += fsCdr.GetSubject() + "|"
|
||||
result += fsCdr.GetAccount() + "|"
|
||||
result += fsCdr.GetDestination() + "|"
|
||||
result += fsCdr.GetTOR() + "|"
|
||||
result += fsCdr.GetAccId() + "|"
|
||||
result += fsCdr.GetTenant() + "|"
|
||||
result += fsCdr.GetReqType() + "|"
|
||||
st, err := fsCdr.GetAnswerTime()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += strconv.FormatInt(st.UnixNano(), 10) + "|"
|
||||
et, err := fsCdr.GetHangupTime()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += strconv.FormatInt(et.UnixNano(), 10) + "|"
|
||||
result += strconv.FormatInt(int64(fsCdr.GetDuration().Seconds()), 10) + "|"
|
||||
return
|
||||
}
|
||||
|
||||
func (fsCdr FSCdr) Restore(input string) error {
|
||||
return errors.New("Not implemented")
|
||||
}
|
||||
|
||||
// Used in extra mediation
|
||||
func (fsCdr FSCdr) AsStoredCdr(runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, setupTimeFld, answerTimeFld, durationFld string, extraFlds []string, fieldsMandatory bool) (*utils.StoredCdr, error) {
|
||||
if utils.IsSliceMember([]string{runId, reqTypeFld, directionFld, tenantFld, torFld, accountFld, subjectFld, destFld, answerTimeFld, durationFld}, "") {
|
||||
return nil, errors.New(fmt.Sprintf("%s:FieldName", utils.ERR_MANDATORY_IE_MISSING)) // All input field names are mandatory
|
||||
}
|
||||
var err error
|
||||
var hasKey bool
|
||||
var sTimeStr, aTimeStr, durStr string
|
||||
rtCdr := new(utils.StoredCdr)
|
||||
rtCdr.MediationRunId = runId
|
||||
rtCdr.Cost = -1.0 // Default for non-rated CDR
|
||||
if rtCdr.AccId = fsCdr.GetAccId(); len(rtCdr.AccId) == 0 {
|
||||
if fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.ACCID))
|
||||
} else { // Not mandatory, need to generate here CgrId
|
||||
rtCdr.CgrId = utils.GenUUID()
|
||||
}
|
||||
} else { // hasKey, use it to generate cgrid
|
||||
rtCdr.CgrId = utils.FSCgrId(rtCdr.AccId)
|
||||
}
|
||||
if rtCdr.CdrHost = fsCdr.GetCdrHost(); len(rtCdr.CdrHost) == 0 && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.CDRHOST))
|
||||
}
|
||||
if rtCdr.CdrSource = fsCdr.GetCdrSource(); len(rtCdr.CdrSource) == 0 && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, utils.CDRSOURCE))
|
||||
}
|
||||
if strings.HasPrefix(reqTypeFld, utils.STATIC_VALUE_PREFIX) { // Values starting with prefix are not dynamically populated
|
||||
rtCdr.ReqType = reqTypeFld[1:]
|
||||
} else if rtCdr.ReqType, hasKey = fsCdr.vars[reqTypeFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, reqTypeFld))
|
||||
}
|
||||
if strings.HasPrefix(directionFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Direction = directionFld[1:]
|
||||
} else if rtCdr.Direction, hasKey = fsCdr.vars[directionFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, directionFld))
|
||||
}
|
||||
if strings.HasPrefix(tenantFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Tenant = tenantFld[1:]
|
||||
} else if rtCdr.Tenant, hasKey = fsCdr.vars[tenantFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, tenantFld))
|
||||
}
|
||||
if strings.HasPrefix(torFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.TOR = torFld[1:]
|
||||
} else if rtCdr.TOR, hasKey = fsCdr.vars[torFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, torFld))
|
||||
}
|
||||
if strings.HasPrefix(accountFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Account = accountFld[1:]
|
||||
} else if rtCdr.Account, hasKey = fsCdr.vars[accountFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, accountFld))
|
||||
}
|
||||
if strings.HasPrefix(subjectFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Subject = subjectFld[1:]
|
||||
} else if rtCdr.Subject, hasKey = fsCdr.vars[subjectFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, subjectFld))
|
||||
}
|
||||
if strings.HasPrefix(destFld, utils.STATIC_VALUE_PREFIX) {
|
||||
rtCdr.Destination = destFld[1:]
|
||||
} else if rtCdr.Destination, hasKey = fsCdr.vars[destFld]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, destFld))
|
||||
}
|
||||
if sTimeStr, hasKey = fsCdr.vars[setupTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, setupTimeFld))
|
||||
} else {
|
||||
if strings.HasPrefix(setupTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
sTimeStr = setupTimeFld[1:]
|
||||
}
|
||||
if rtCdr.SetupTime, err = utils.ParseTimeDetectLayout(sTimeStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if aTimeStr, hasKey = fsCdr.vars[answerTimeFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, answerTimeFld))
|
||||
} else {
|
||||
if strings.HasPrefix(answerTimeFld, utils.STATIC_VALUE_PREFIX) {
|
||||
aTimeStr = answerTimeFld[1:]
|
||||
}
|
||||
if rtCdr.AnswerTime, err = utils.ParseTimeDetectLayout(aTimeStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if durStr, hasKey = fsCdr.vars[durationFld]; !hasKey && fieldsMandatory && !strings.HasPrefix(durationFld, utils.STATIC_VALUE_PREFIX) {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, durationFld))
|
||||
} else {
|
||||
if strings.HasPrefix(durationFld, utils.STATIC_VALUE_PREFIX) {
|
||||
durStr = durationFld[1:]
|
||||
}
|
||||
if rtCdr.Duration, err = utils.ParseDurationWithSecs(durStr); err != nil && fieldsMandatory {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rtCdr.ExtraFields = make(map[string]string, len(extraFlds))
|
||||
for _, fldName := range extraFlds {
|
||||
if fldVal, hasKey := fsCdr.vars[fldName]; !hasKey && fieldsMandatory {
|
||||
return nil, errors.New(fmt.Sprintf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, fldName))
|
||||
} else {
|
||||
rtCdr.ExtraFields[fldName] = fldVal
|
||||
}
|
||||
}
|
||||
return rtCdr, nil
|
||||
}
|
||||
@@ -22,29 +22,87 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/cgrates/cgrates/console"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
|
||||
"io"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cgrates/cgrates/console"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"github.com/cgrates/liner"
|
||||
)
|
||||
|
||||
var (
|
||||
history_fn = os.Getenv("HOME") + "/.cgr_history"
|
||||
version = flag.Bool("version", false, "Prints the application version.")
|
||||
verbose = flag.Bool("verbose", false, "Show extra info about command execution.")
|
||||
server = flag.String("server", "127.0.0.1:2012", "server address host:port")
|
||||
rpc_encoding = flag.String("rpc_encoding", "json", "RPC encoding used <gob|json>")
|
||||
client *rpc.Client
|
||||
)
|
||||
|
||||
func executeCommand(command string) {
|
||||
if strings.TrimSpace(command) == "" {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(command) == "help" {
|
||||
commands := console.GetCommands()
|
||||
fmt.Println("Commands:")
|
||||
for name, cmd := range commands {
|
||||
fmt.Print(name, cmd.Usage())
|
||||
}
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(command, "help") {
|
||||
words := strings.Split(command, " ")
|
||||
if len(words) > 1 {
|
||||
commands := console.GetCommands()
|
||||
if cmd, ok := commands[words[1]]; ok {
|
||||
fmt.Print(cmd.Usage())
|
||||
} else {
|
||||
fmt.Print("Available commands: ")
|
||||
for name, _ := range commands {
|
||||
fmt.Print(name + " ")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
cmd, cmdErr := console.GetCommandValue(command, *verbose)
|
||||
if cmdErr != nil {
|
||||
fmt.Println(cmdErr)
|
||||
return
|
||||
}
|
||||
if cmd.RpcMethod() != "" {
|
||||
res := cmd.RpcResult()
|
||||
param := cmd.RpcParams()
|
||||
//log.Print(reflect.TypeOf(param))
|
||||
switch param.(type) {
|
||||
case *console.StringWrapper:
|
||||
param = param.(*console.StringWrapper).Item
|
||||
}
|
||||
//log.Printf("Param: %+v", param)
|
||||
if rpcErr := client.Call(cmd.RpcMethod(), param, res); rpcErr != nil {
|
||||
fmt.Println("Error executing command: " + rpcErr.Error())
|
||||
} else {
|
||||
result, _ := json.MarshalIndent(res, "", " ")
|
||||
fmt.Println(string(result))
|
||||
}
|
||||
} else {
|
||||
fmt.Println(cmd.LocalExecute())
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *version {
|
||||
fmt.Println("CGRateS " + utils.VERSION)
|
||||
return
|
||||
}
|
||||
var client *rpc.Client
|
||||
|
||||
var err error
|
||||
if *rpc_encoding == "json" {
|
||||
client, err = jsonrpc.Dial("tcp", *server)
|
||||
@@ -56,17 +114,69 @@ func main() {
|
||||
log.Fatal("Could not connect to server " + *server)
|
||||
}
|
||||
defer client.Close()
|
||||
// Strict command parsing starts here
|
||||
args := append([]string{os.Args[0]}, flag.Args()...) // Emulate os.Args by prepending the cmd to list of args coming from flag
|
||||
cmd, cmdErr := console.GetCommandValue(args)
|
||||
if cmdErr != nil {
|
||||
log.Fatal(cmdErr)
|
||||
}
|
||||
res := cmd.RpcResult()
|
||||
if rpcErr := client.Call(cmd.RpcMethod(), cmd.RpcParams(), res); rpcErr != nil {
|
||||
fmt.Println("Error executing command: " + rpcErr.Error())
|
||||
}
|
||||
result, _ := json.MarshalIndent(res, "", " ")
|
||||
fmt.Println(string(result))
|
||||
|
||||
if len(flag.Args()) != 0 {
|
||||
executeCommand(strings.Join(flag.Args(), " "))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Welcome to CGRateS console!")
|
||||
fmt.Println("Type `help` for a list of commands\n")
|
||||
|
||||
line := liner.NewLiner()
|
||||
defer line.Close()
|
||||
|
||||
line.SetCompleter(func(line string) (comp []string) {
|
||||
commands := console.GetCommands()
|
||||
for name, cmd := range commands {
|
||||
if strings.HasPrefix(name, strings.ToLower(line)) {
|
||||
comp = append(comp, name)
|
||||
}
|
||||
// try arguments
|
||||
if strings.HasPrefix(line, name) {
|
||||
// get last word
|
||||
lastSpace := strings.LastIndex(line, " ")
|
||||
lastSpace += 1
|
||||
for _, arg := range cmd.ClientArgs() {
|
||||
if strings.HasPrefix(arg, line[lastSpace:]) {
|
||||
comp = append(comp, line[:lastSpace]+arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
if f, err := os.Open(history_fn); err == nil {
|
||||
line.ReadHistory(f)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
stop := false
|
||||
for !stop {
|
||||
if command, err := line.Prompt("cgr> "); err != nil {
|
||||
if err == io.EOF {
|
||||
fmt.Println("\nbye!")
|
||||
stop = true
|
||||
} else {
|
||||
fmt.Print("Error reading line: ", err)
|
||||
}
|
||||
} else {
|
||||
line.AppendHistory(command)
|
||||
switch strings.ToLower(strings.TrimSpace(command)) {
|
||||
case "quit", "exit", "bye", "close":
|
||||
fmt.Println("\nbye!")
|
||||
stop = true
|
||||
default:
|
||||
executeCommand(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f, err := os.Create(history_fn); err != nil {
|
||||
log.Print("Error writing history file: ", err)
|
||||
} else {
|
||||
line.WriteHistory(f)
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,23 +23,21 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"runtime"
|
||||
//"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/balancer2go"
|
||||
"github.com/cgrates/cgrates/cdrc"
|
||||
"github.com/cgrates/cgrates/cdrs"
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/history"
|
||||
"github.com/cgrates/cgrates/mediator"
|
||||
"github.com/cgrates/cgrates/scheduler"
|
||||
"github.com/cgrates/cgrates/sessionmanager"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"github.com/cgrates/rpcclient"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,6 +49,7 @@ const (
|
||||
REDIS = "redis"
|
||||
SAME = "same"
|
||||
FS = "freeswitch"
|
||||
OSIPS = "opensips"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -66,20 +65,21 @@ var (
|
||||
exitChan = make(chan bool)
|
||||
server = &engine.Server{}
|
||||
scribeServer history.Scribe
|
||||
cdrServer *cdrs.CDRS
|
||||
cdrServer *engine.CDRS
|
||||
cdrStats *engine.Stats
|
||||
sm sessionmanager.SessionManager
|
||||
medi *mediator.Mediator
|
||||
medi *engine.Mediator
|
||||
cfg *config.CGRConfig
|
||||
err error
|
||||
)
|
||||
|
||||
func cacheData(ratingDb engine.RatingStorage, accountDb engine.AccountingStorage, doneChan chan struct{}) {
|
||||
if err := ratingDb.CacheRating(nil, nil, nil, nil); err != nil {
|
||||
if err := ratingDb.CacheRating(nil, nil, nil, nil, nil); err != nil {
|
||||
engine.Logger.Crit(fmt.Sprintf("Cache rating error: %s", err.Error()))
|
||||
exitChan <- true
|
||||
return
|
||||
}
|
||||
if err := accountDb.CacheAccounting(nil, nil, nil); err != nil {
|
||||
if err := accountDb.CacheAccounting(nil, nil, nil, nil); err != nil {
|
||||
engine.Logger.Crit(fmt.Sprintf("Cache accounting error: %s", err.Error()))
|
||||
exitChan <- true
|
||||
return
|
||||
@@ -93,11 +93,11 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
|
||||
<-cacheChan // Cache needs to come up before we are ready
|
||||
connector = responder
|
||||
} else {
|
||||
var client *rpc.Client
|
||||
var client *rpcclient.RpcClient
|
||||
var err error
|
||||
|
||||
for i := 0; i < cfg.MediatorRaterReconnects; i++ {
|
||||
client, err = rpc.Dial("tcp", cfg.MediatorRater)
|
||||
for i := 0; i < cfg.MediatorReconnects; i++ {
|
||||
client, err = rpcclient.NewRpcClient("tcp", cfg.MediatorRater, 0, cfg.MediatorReconnects, utils.GOB)
|
||||
if err == nil { //Connected so no need to reiterate
|
||||
break
|
||||
}
|
||||
@@ -111,23 +111,24 @@ func startMediator(responder *engine.Responder, loggerDb engine.LogStorage, cdrD
|
||||
connector = &engine.RPCClientConnector{Client: client}
|
||||
}
|
||||
var err error
|
||||
medi, err = mediator.NewMediator(connector, loggerDb, cdrDb, cfg)
|
||||
medi, err = engine.NewMediator(connector, loggerDb, cdrDb, cdrStats, cfg)
|
||||
if err != nil {
|
||||
engine.Logger.Crit(fmt.Sprintf("Mediator config parsing error: %v", err))
|
||||
exitChan <- true
|
||||
return
|
||||
}
|
||||
engine.Logger.Info("Registering Mediator RPC service.")
|
||||
server.RpcRegister(&mediator.MediatorV1{Medi: medi})
|
||||
server.RpcRegister(&apier.MediatorV1{Medi: medi})
|
||||
|
||||
close(chanDone)
|
||||
}
|
||||
|
||||
func startCdrc(cdrsChan chan struct{}) {
|
||||
if cfg.CdrcCdrs == utils.INTERNAL {
|
||||
// Fires up a cdrc instance
|
||||
func startCdrc(cdrsChan chan struct{}, cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId string, runDelay time.Duration, csvSep string, cdrFields map[string][]*utils.RSRField) {
|
||||
if cdrsAddress == utils.INTERNAL {
|
||||
<-cdrsChan // Wait for CDRServer to come up before start processing
|
||||
}
|
||||
cdrc, err := cdrc.NewCdrc(cfg, cdrServer)
|
||||
cdrc, err := cdrc.NewCdrc(cdrsAddress, cdrType, cdrInDir, cdrOutDir, cdrSourceId, runDelay, csvSep, cdrFields, cdrServer)
|
||||
if err != nil {
|
||||
engine.Logger.Crit(fmt.Sprintf("Cdrc config parsing error: %s", err.Error()))
|
||||
exitChan <- true
|
||||
@@ -140,16 +141,15 @@ func startCdrc(cdrsChan chan struct{}) {
|
||||
}
|
||||
|
||||
func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage, cacheChan chan struct{}) {
|
||||
var connector engine.Connector
|
||||
var raterConn, cdrsConn engine.Connector
|
||||
var client *rpcclient.RpcClient
|
||||
if cfg.SMRater == utils.INTERNAL {
|
||||
<-cacheChan // Wait for the cache to init before start doing queries
|
||||
connector = responder
|
||||
raterConn = responder
|
||||
} else {
|
||||
var client *rpc.Client
|
||||
var err error
|
||||
|
||||
for i := 0; i < cfg.SMRaterReconnects; i++ {
|
||||
client, err = rpc.Dial("tcp", cfg.SMRater)
|
||||
for i := 0; i < cfg.SMReconnects; i++ {
|
||||
client, err = rpcclient.NewRpcClient("tcp", cfg.SMRater, 0, cfg.SMReconnects, utils.GOB)
|
||||
if err == nil { //Connected so no need to reiterate
|
||||
break
|
||||
}
|
||||
@@ -159,18 +159,39 @@ func startSessionManager(responder *engine.Responder, loggerDb engine.LogStorage
|
||||
engine.Logger.Crit(fmt.Sprintf("<SessionManager> Could not connect to engine: %v", err))
|
||||
exitChan <- true
|
||||
}
|
||||
connector = &engine.RPCClientConnector{Client: client}
|
||||
raterConn = &engine.RPCClientConnector{Client: client}
|
||||
}
|
||||
if cfg.SMCdrS == cfg.SMRater {
|
||||
cdrsConn = raterConn
|
||||
} else if cfg.SMCdrS == utils.INTERNAL {
|
||||
<-cacheChan // Wait for the cache to init before start doing queries
|
||||
cdrsConn = responder
|
||||
} else if len(cfg.SMCdrS) != 0 {
|
||||
for i := 0; i < cfg.SMReconnects; i++ {
|
||||
client, err = rpcclient.NewRpcClient("tcp", cfg.SMCdrS, 0, cfg.SMReconnects, utils.GOB)
|
||||
if err == nil { //Connected so no need to reiterate
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(i+1) * time.Second)
|
||||
}
|
||||
if err != nil {
|
||||
engine.Logger.Crit(fmt.Sprintf("<SM-OpenSIPS> Could not connect to CDRS via RPC: %v", err))
|
||||
exitChan <- true
|
||||
}
|
||||
cdrsConn = &engine.RPCClientConnector{Client: client}
|
||||
}
|
||||
switch cfg.SMSwitchType {
|
||||
case FS:
|
||||
dp, _ := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval))
|
||||
sm = sessionmanager.NewFSSessionManager(loggerDb, connector, dp)
|
||||
errConn := sm.Connect(cfg)
|
||||
if errConn != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<SessionManager> error: %s!", errConn))
|
||||
}
|
||||
sm = sessionmanager.NewFSSessionManager(cfg, loggerDb, raterConn, cdrsConn, dp)
|
||||
case OSIPS:
|
||||
sm, _ = sessionmanager.NewOSipsSessionManager(cfg, raterConn, cdrsConn)
|
||||
default:
|
||||
engine.Logger.Err(fmt.Sprintf("<SessionManager> Unsupported session manger type: %s!", cfg.SMSwitchType))
|
||||
exitChan <- true
|
||||
}
|
||||
if err = sm.Connect(); err != nil {
|
||||
engine.Logger.Err(fmt.Sprintf("<SessionManager> error: %s!", err))
|
||||
}
|
||||
exitChan <- true
|
||||
}
|
||||
@@ -184,8 +205,11 @@ func startCDRS(responder *engine.Responder, cdrDb engine.CdrStorage, mediChan, d
|
||||
return
|
||||
}
|
||||
}
|
||||
cdrServer = cdrs.New(cdrDb, medi, cfg)
|
||||
cdrServer = engine.NewCdrS(cdrDb, medi, cdrStats, cfg)
|
||||
cdrServer.RegisterHanlersToServer(server)
|
||||
engine.Logger.Info("Registering CDRS RPC service.")
|
||||
server.RpcRegister(&apier.CDRSV1{CdrSrv: cdrServer})
|
||||
responder.CdrSrv = cdrServer // Make the cdrserver available for internal communication
|
||||
close(doneChan)
|
||||
}
|
||||
|
||||
@@ -200,7 +224,7 @@ func startHistoryServer(chanDone chan struct{}) {
|
||||
}
|
||||
|
||||
// chanStartServer will report when server is up, useful for internal requests
|
||||
func startHistoryAgent(scribeServer history.Scribe, chanServerStarted chan struct{}) {
|
||||
func startHistoryAgent(chanServerStarted chan struct{}) {
|
||||
if cfg.HistoryServer == utils.INTERNAL { // For internal requests, wait for server to come online before connecting
|
||||
engine.Logger.Crit(fmt.Sprintf("<HistoryAgent> Connecting internally to HistoryServer"))
|
||||
select {
|
||||
@@ -224,7 +248,7 @@ func startHistoryAgent(scribeServer history.Scribe, chanServerStarted chan struc
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
}
|
||||
}
|
||||
engine.SetHistoryScribe(scribeServer)
|
||||
engine.SetHistoryScribe(scribeServer) // scribeServer comes from global variable
|
||||
return
|
||||
}
|
||||
|
||||
@@ -287,9 +311,9 @@ func main() {
|
||||
if *pidFile != "" {
|
||||
writePid()
|
||||
}
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
// runtime.GOMAXPROCS(runtime.NumCPU()) // For now it slows down computing due to CPU management, to be reviewed in future Go releases
|
||||
|
||||
cfg, err = config.NewCGRConfig(cfgPath)
|
||||
cfg, err = config.NewCGRConfigFromFile(cfgPath)
|
||||
if err != nil {
|
||||
engine.Logger.Crit(fmt.Sprintf("Could not parse config: %s exiting!", err))
|
||||
return
|
||||
@@ -355,7 +379,7 @@ func main() {
|
||||
loadDb = logDb.(engine.LoadStorage)
|
||||
cdrDb = logDb.(engine.CdrStorage)
|
||||
|
||||
engine.SetRoundingMethodAndDecimals(cfg.RoundingMethod, cfg.RoundingDecimals)
|
||||
engine.SetRoundingDecimals(cfg.RoundingDecimals)
|
||||
if cfg.SMDebitInterval > 0 {
|
||||
if dp, err := time.ParseDuration(fmt.Sprintf("%vs", cfg.SMDebitInterval)); err == nil {
|
||||
engine.SetDebitPeriod(dp)
|
||||
@@ -382,13 +406,22 @@ func main() {
|
||||
stopHandled = true
|
||||
}
|
||||
|
||||
if cfg.CDRStatsEnabled { // Init it here so we make it availabe to the Apier
|
||||
cdrStats = engine.NewStats(ratingDb)
|
||||
if cfg.CDRStatConfig != nil && len(cfg.CDRStatConfig.Metrics) != 0 {
|
||||
cdrStats.AddQueue(engine.NewCdrStatsFromCdrStatsCfg(cfg.CDRStatConfig), nil)
|
||||
}
|
||||
server.RpcRegister(cdrStats)
|
||||
server.RpcRegister(&apier.CDRStatsV1{cdrStats}) // Public APIs
|
||||
}
|
||||
|
||||
responder := &engine.Responder{ExitChan: exitChan}
|
||||
apier := &apier.ApierV1{StorDb: loadDb, RatingDb: ratingDb, AccountDb: accountDb, CdrDb: cdrDb, LogDb: logDb, Config: cfg}
|
||||
apierRpc := &apier.ApierV1{StorDb: loadDb, RatingDb: ratingDb, AccountDb: accountDb, CdrDb: cdrDb, LogDb: logDb, Config: cfg, Responder: responder, CdrStatsSrv: cdrStats}
|
||||
|
||||
if cfg.RaterEnabled && !cfg.BalancerEnabled && cfg.RaterBalancer != utils.INTERNAL {
|
||||
engine.Logger.Info("Registering Rater service")
|
||||
server.RpcRegister(responder)
|
||||
server.RpcRegister(apier)
|
||||
server.RpcRegister(apierRpc)
|
||||
}
|
||||
|
||||
if cfg.BalancerEnabled {
|
||||
@@ -397,7 +430,7 @@ func main() {
|
||||
stopHandled = true
|
||||
responder.Bal = bal
|
||||
server.RpcRegister(responder)
|
||||
server.RpcRegister(apier)
|
||||
server.RpcRegister(apierRpc)
|
||||
if cfg.RaterEnabled {
|
||||
engine.Logger.Info("<Balancer> Registering internal rater")
|
||||
bal.AddClient("local", new(engine.ResponderWorker))
|
||||
@@ -413,7 +446,7 @@ func main() {
|
||||
go func() {
|
||||
sched := scheduler.NewScheduler()
|
||||
go reloadSchedulerSingnalHandler(sched, accountDb)
|
||||
apier.Sched = sched
|
||||
apierRpc.Sched = sched
|
||||
sched.LoadActionTimings(accountDb)
|
||||
sched.Loop()
|
||||
}()
|
||||
@@ -428,7 +461,7 @@ func main() {
|
||||
|
||||
if cfg.HistoryAgentEnabled {
|
||||
engine.Logger.Info("Starting CGRateS History Agent.")
|
||||
go startHistoryAgent(scribeServer, histServChan)
|
||||
go startHistoryAgent(histServChan)
|
||||
}
|
||||
|
||||
var medChan chan struct{}
|
||||
@@ -452,10 +485,23 @@ func main() {
|
||||
// close all sessions on shutdown
|
||||
go shutdownSessionmanagerSingnalHandler()
|
||||
}
|
||||
|
||||
if cfg.CdrcEnabled {
|
||||
var cdrcEnabled bool
|
||||
if cfg.CdrcEnabled { // Start default cdrc configured in csv here
|
||||
cdrcEnabled = true
|
||||
go startCdrc(cdrsChan, cfg.CdrcCdrs, cfg.CdrcCdrType, cfg.CdrcCdrInDir, cfg.CdrcCdrOutDir, cfg.CdrcSourceId, cfg.CdrcRunDelay, cfg.CdrcCsvSep, cfg.CdrcCdrFields)
|
||||
}
|
||||
if cfg.XmlCfgDocument != nil {
|
||||
for _, xmlCdrc := range cfg.XmlCfgDocument.GetCdrcCfgs("") {
|
||||
if !xmlCdrc.Enabled {
|
||||
continue
|
||||
}
|
||||
cdrcEnabled = true
|
||||
go startCdrc(cdrsChan, xmlCdrc.CdrsAddress, xmlCdrc.CdrType, xmlCdrc.CdrInDir, xmlCdrc.CdrOutDir,
|
||||
xmlCdrc.CdrSourceId, time.Duration(xmlCdrc.RunDelay), xmlCdrc.CsvSeparator, xmlCdrc.CdrRSRFields())
|
||||
}
|
||||
}
|
||||
if cdrcEnabled {
|
||||
engine.Logger.Info("Starting CGRateS CDR client.")
|
||||
go startCdrc(cdrsChan)
|
||||
}
|
||||
|
||||
// Start the servers
|
||||
|
||||
@@ -125,8 +125,10 @@ func shutdownSessionmanagerSingnalHandler() {
|
||||
signal.Notify(c, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
|
||||
<-c
|
||||
|
||||
if err := sm.Shutdown(); err != nil {
|
||||
engine.Logger.Warning(fmt.Sprintf("<SessionManager> %s", err))
|
||||
if sm != nil {
|
||||
if err := sm.Shutdown(); err != nil {
|
||||
engine.Logger.Warning(fmt.Sprintf("<SessionManager> %s", err))
|
||||
}
|
||||
}
|
||||
exitChan <- true
|
||||
}
|
||||
|
||||
@@ -57,18 +57,19 @@ var (
|
||||
|
||||
dbdata_encoding = flag.String("dbdata_encoding", cgrConfig.DBDataEncoding, "The encoding used to store object data in strings")
|
||||
|
||||
flush = flag.Bool("flushdb", false, "Flush the database before importing")
|
||||
tpid = flag.String("tpid", "", "The tariff plan id from the database")
|
||||
dataPath = flag.String("path", "./", "The path to folder containing the data files")
|
||||
version = flag.Bool("version", false, "Prints the application version.")
|
||||
verbose = flag.Bool("verbose", false, "Enable detailed verbose logging output")
|
||||
dryRun = flag.Bool("dry_run", false, "When true will not save loaded data to dataDb but just parse it for consistency and errors.")
|
||||
stats = flag.Bool("stats", false, "Generates statsistics about given data.")
|
||||
fromStorDb = flag.Bool("from_stordb", false, "Load the tariff plan from storDb to dataDb")
|
||||
toStorDb = flag.Bool("to_stordb", false, "Import the tariff plan from files to storDb")
|
||||
historyServer = flag.String("history_server", cgrConfig.RPCGOBListen, "The history server address:port, empty to disable automaticautomatic history archiving")
|
||||
raterAddress = flag.String("rater_address", cgrConfig.RPCGOBListen, "Rater service to contact for cache reloads, empty to disable automatic cache reloads")
|
||||
runId = flag.String("runid", "", "Uniquely identify an import/load, postpended to some automatic fields")
|
||||
flush = flag.Bool("flushdb", false, "Flush the database before importing")
|
||||
tpid = flag.String("tpid", "", "The tariff plan id from the database")
|
||||
dataPath = flag.String("path", "./", "The path to folder containing the data files")
|
||||
version = flag.Bool("version", false, "Prints the application version.")
|
||||
verbose = flag.Bool("verbose", false, "Enable detailed verbose logging output")
|
||||
dryRun = flag.Bool("dry_run", false, "When true will not save loaded data to dataDb but just parse it for consistency and errors.")
|
||||
stats = flag.Bool("stats", false, "Generates statsistics about given data.")
|
||||
fromStorDb = flag.Bool("from_stordb", false, "Load the tariff plan from storDb to dataDb")
|
||||
toStorDb = flag.Bool("to_stordb", false, "Import the tariff plan from files to storDb")
|
||||
historyServer = flag.String("history_server", cgrConfig.RPCGOBListen, "The history server address:port, empty to disable automaticautomatic history archiving")
|
||||
raterAddress = flag.String("rater_address", cgrConfig.RPCGOBListen, "Rater service to contact for cache reloads, empty to disable automatic cache reloads")
|
||||
cdrstatsAddress = flag.String("cdrstats_address", cgrConfig.RPCGOBListen, "CDRStats service to contact for data reloads, empty to disable automatic data reloads")
|
||||
runId = flag.String("runid", "", "Uniquely identify an import/load, postpended to some automatic fields")
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -81,7 +82,7 @@ func main() {
|
||||
var ratingDb engine.RatingStorage
|
||||
var accountDb engine.AccountingStorage
|
||||
var storDb engine.LoadStorage
|
||||
var rater *rpc.Client
|
||||
var rater, cdrstats *rpc.Client
|
||||
var loader engine.TPLoader
|
||||
// Init necessary db connections, only if not already
|
||||
if !*dryRun { // make sure we do not need db connections on dry run, also not importing into any stordb
|
||||
@@ -137,10 +138,13 @@ func main() {
|
||||
path.Join(*dataPath, utils.RATING_PLANS_CSV),
|
||||
path.Join(*dataPath, utils.RATING_PROFILES_CSV),
|
||||
path.Join(*dataPath, utils.SHARED_GROUPS_CSV),
|
||||
path.Join(*dataPath, utils.LCRS_CSV),
|
||||
path.Join(*dataPath, utils.ACTIONS_CSV),
|
||||
path.Join(*dataPath, utils.ACTION_PLANS_CSV),
|
||||
path.Join(*dataPath, utils.ACTION_TRIGGERS_CSV),
|
||||
path.Join(*dataPath, utils.ACCOUNT_ACTIONS_CSV))
|
||||
path.Join(*dataPath, utils.ACCOUNT_ACTIONS_CSV),
|
||||
path.Join(*dataPath, utils.DERIVED_CHARGERS_CSV),
|
||||
path.Join(*dataPath, utils.CDR_STATS_CSV))
|
||||
}
|
||||
err = loader.LoadAll()
|
||||
if err != nil {
|
||||
@@ -172,6 +176,19 @@ func main() {
|
||||
} else {
|
||||
log.Print("WARNING: Rates automatic cache reloading is disabled!")
|
||||
}
|
||||
if *cdrstatsAddress != "" { // Init connection to rater so we can reload it's data
|
||||
if *cdrstatsAddress == *raterAddress {
|
||||
cdrstats = rater
|
||||
} else {
|
||||
cdrstats, err = rpc.Dial("tcp", *cdrstatsAddress)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to CDRStats API: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Print("WARNING: CDRStats automatic data reload is disabled!")
|
||||
}
|
||||
|
||||
// write maps to database
|
||||
if err := loader.WriteToDatabase(*flush, *verbose); err != nil {
|
||||
@@ -190,12 +207,17 @@ func main() {
|
||||
shgIds, _ := loader.GetLoadedIds(engine.SHARED_GROUP_PREFIX)
|
||||
rpAliases, _ := loader.GetLoadedIds(engine.RP_ALIAS_PREFIX)
|
||||
accAliases, _ := loader.GetLoadedIds(engine.ACC_ALIAS_PREFIX)
|
||||
lcrIds, _ := loader.GetLoadedIds(engine.LCR_PREFIX)
|
||||
dcs, _ := loader.GetLoadedIds(engine.DERIVEDCHARGERS_PREFIX)
|
||||
// Reload cache first since actions could be calling info from within
|
||||
if *verbose {
|
||||
log.Print("Reloading cache")
|
||||
}
|
||||
if err = rater.Call("ApierV1.ReloadCache", utils.ApiReloadCache{dstIds, rplIds, rpfIds, actIds, shgIds, rpAliases, accAliases}, &reply); err != nil {
|
||||
log.Fatalf("Got error on cache reload: %s", err.Error())
|
||||
if *flush {
|
||||
dstIds, rplIds, rpfIds, rpAliases, lcrIds = nil, nil, nil, nil, nil // Should reload all these on flush
|
||||
}
|
||||
if err = rater.Call("ApierV1.ReloadCache", utils.ApiReloadCache{dstIds, rplIds, rpfIds, actIds, shgIds, rpAliases, accAliases, lcrIds, dcs}, &reply); err != nil {
|
||||
log.Printf("WARNING: Got error on cache reload: %s\n", err.Error())
|
||||
}
|
||||
actTmgIds, _ := loader.GetLoadedIds(engine.ACTION_TIMING_PREFIX)
|
||||
if len(actTmgIds) != 0 {
|
||||
@@ -203,9 +225,24 @@ func main() {
|
||||
log.Print("Reloading scheduler")
|
||||
}
|
||||
if err = rater.Call("ApierV1.ReloadScheduler", "", &reply); err != nil {
|
||||
log.Fatalf("Got error on scheduler reload: %s", err.Error())
|
||||
log.Printf("WARNING: Got error on scheduler reload: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if cdrstats != nil {
|
||||
statsQueueIds, _ := loader.GetLoadedIds(engine.CDR_STATS_PREFIX)
|
||||
if *flush {
|
||||
statsQueueIds = []string{} // Force reload all
|
||||
}
|
||||
if len(statsQueueIds) != 0 {
|
||||
if *verbose {
|
||||
log.Print("Reloading CDRStats data")
|
||||
}
|
||||
var reply string
|
||||
if err := cdrstats.Call("CDRStatsV1.ReloadQueues", utils.AttrCDRStatsReloadQueues{StatsQueueIds: statsQueueIds}, &reply); err != nil {
|
||||
log.Printf("WARNING: Failed reloading stat queues, error: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -52,10 +53,11 @@ var (
|
||||
accountdb_pass = flag.String("accountdb_passwd", cgrConfig.AccountDBPass, "The AccountingDb user's password.")
|
||||
dbdata_encoding = flag.String("dbdata_encoding", cgrConfig.DBDataEncoding, "The encoding used to store object data in strings.")
|
||||
raterAddress = flag.String("rater_address", "", "Rater address for remote tests. Empty for internal rater.")
|
||||
tor = flag.String("tor", "call", "The type of record to use in queries.")
|
||||
tenant = flag.String("tenant", "call", "The type of record to use in queries.")
|
||||
tor = flag.String("tor", utils.VOICE, "The type of record to use in queries.")
|
||||
category = flag.String("category", "call", "The Record category to test.")
|
||||
tenant = flag.String("tenant", "cgrates.org", "The type of record to use in queries.")
|
||||
subject = flag.String("subject", "1001", "The rating subject to use in queries.")
|
||||
destination = flag.String("destination", "+4986517174963", "The destination to use in queries.")
|
||||
destination = flag.String("destination", "1002", "The destination to use in queries.")
|
||||
|
||||
nilDuration = time.Duration(0)
|
||||
)
|
||||
@@ -73,7 +75,7 @@ func durInternalRater(cd *engine.CallDescriptor) (time.Duration, error) {
|
||||
}
|
||||
defer accountDb.Close()
|
||||
engine.SetAccountingStorage(accountDb)
|
||||
if err := ratingDb.CacheRating(nil, nil, nil, nil); err != nil {
|
||||
if err := ratingDb.CacheRating(nil, nil, nil, nil, nil); err != nil {
|
||||
return nilDuration, fmt.Errorf("Cache rating error: %s", err.Error())
|
||||
}
|
||||
log.Printf("Runnning %d cycles...", *runs)
|
||||
@@ -150,14 +152,15 @@ func main() {
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
cd := &engine.CallDescriptor{
|
||||
TimeStart: time.Date(2013, time.December, 13, 22, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2013, time.December, 13, 22, 31, 0, 0, time.UTC),
|
||||
CallDuration: 60 * time.Second,
|
||||
Direction: "*out",
|
||||
TOR: *tor,
|
||||
Tenant: *tenant,
|
||||
Subject: *subject,
|
||||
Destination: *destination,
|
||||
TimeStart: time.Date(2014, time.December, 11, 55, 30, 0, 0, time.UTC),
|
||||
TimeEnd: time.Date(2014, time.December, 11, 55, 31, 0, 0, time.UTC),
|
||||
DurationIndex: 60 * time.Second,
|
||||
Direction: "*out",
|
||||
TOR: *tor,
|
||||
Category: *category,
|
||||
Tenant: *tenant,
|
||||
Subject: *subject,
|
||||
Destination: *destination,
|
||||
}
|
||||
var duration time.Duration
|
||||
var err error
|
||||
|
||||
244
config/cdreconfig.go
Normal file
244
config/cdreconfig.go
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Converts a list of field identifiers into proper CDR field content
|
||||
func NewCdreCdrFieldsFromIds(withFixedWith bool, fldsIds ...string) ([]*CdreCdrField, error) {
|
||||
cdrFields := make([]*CdreCdrField, len(fldsIds))
|
||||
for idx, fldId := range fldsIds {
|
||||
if parsedRsr, err := utils.NewRSRField(fldId); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cdrFld := &CdreCdrField{Name: fldId, Type: utils.CDRFIELD, Value: fldId, valueAsRsrField: parsedRsr}
|
||||
if err := cdrFld.setDefaultFieldProperties(withFixedWith); err != nil { // Set default fixed width properties to be used later if needed
|
||||
return nil, err
|
||||
|
||||
}
|
||||
cdrFields[idx] = cdrFld
|
||||
}
|
||||
}
|
||||
return cdrFields, nil
|
||||
}
|
||||
|
||||
func NewDefaultCdreConfig() (*CdreConfig, error) {
|
||||
cdreCfg := new(CdreConfig)
|
||||
if err := cdreCfg.setDefaults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cdreCfg, nil
|
||||
}
|
||||
|
||||
// One instance of CdrExporter
|
||||
type CdreConfig struct {
|
||||
CdrFormat string
|
||||
FieldSeparator rune
|
||||
DataUsageMultiplyFactor float64
|
||||
CostMultiplyFactor float64
|
||||
CostRoundingDecimals int
|
||||
CostShiftDigits int
|
||||
MaskDestId string
|
||||
MaskLength int
|
||||
ExportDir string
|
||||
HeaderFields []*CdreCdrField
|
||||
ContentFields []*CdreCdrField
|
||||
TrailerFields []*CdreCdrField
|
||||
}
|
||||
|
||||
// Set here defaults
|
||||
func (cdreCfg *CdreConfig) setDefaults() error {
|
||||
cdreCfg.CdrFormat = utils.CSV
|
||||
cdreCfg.FieldSeparator = utils.CSV_SEP
|
||||
cdreCfg.DataUsageMultiplyFactor = 0.0
|
||||
cdreCfg.CostMultiplyFactor = 0.0
|
||||
cdreCfg.CostRoundingDecimals = -1
|
||||
cdreCfg.CostShiftDigits = 0
|
||||
cdreCfg.MaskDestId = ""
|
||||
cdreCfg.MaskLength = 0
|
||||
cdreCfg.ExportDir = "/var/log/cgrates/cdre"
|
||||
if flds, err := NewCdreCdrFieldsFromIds(false, utils.CGRID, utils.MEDI_RUNID, utils.TOR, utils.ACCID, utils.REQTYPE, utils.DIRECTION, utils.TENANT,
|
||||
utils.CATEGORY, utils.ACCOUNT, utils.SUBJECT, utils.DESTINATION, utils.SETUP_TIME, utils.ANSWER_TIME, utils.USAGE, utils.COST); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cdreCfg.ContentFields = flds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CdreCdrField struct {
|
||||
Name string
|
||||
Type string
|
||||
Value string
|
||||
Width int
|
||||
Strip string
|
||||
Padding string
|
||||
Layout string
|
||||
Filter *utils.RSRField
|
||||
Mandatory bool
|
||||
valueAsRsrField *utils.RSRField // Cached if the need arrises
|
||||
}
|
||||
|
||||
func (cdrField *CdreCdrField) ValueAsRSRField() *utils.RSRField {
|
||||
return cdrField.valueAsRsrField
|
||||
}
|
||||
|
||||
// Should be called on .fwv configuration without providing default values for fixed with parameters
|
||||
func (cdrField *CdreCdrField) setDefaultFieldProperties(fixedWidth bool) error {
|
||||
if cdrField.valueAsRsrField == nil {
|
||||
return errors.New("Missing valueAsRsrField")
|
||||
}
|
||||
switch cdrField.valueAsRsrField.Id {
|
||||
case utils.CGRID:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 40
|
||||
}
|
||||
case utils.ORDERID:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 11
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.TOR:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 6
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.ACCID:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 36
|
||||
cdrField.Strip = "left"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.CDRHOST:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 15
|
||||
cdrField.Strip = "left"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.CDRSOURCE:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 15
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.REQTYPE:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 13
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.DIRECTION:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 4
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.TENANT:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 24
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.CATEGORY:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 10
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.ACCOUNT:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 24
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.SUBJECT:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 24
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.DESTINATION:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 24
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.SETUP_TIME:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 30
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
cdrField.Layout = "2006-01-02T15:04:05Z07:00"
|
||||
}
|
||||
case utils.ANSWER_TIME:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 30
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
cdrField.Layout = "2006-01-02T15:04:05Z07:00"
|
||||
}
|
||||
case utils.USAGE:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 30
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.MEDI_RUNID:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 20
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
case utils.COST:
|
||||
cdrField.Mandatory = true
|
||||
if fixedWidth {
|
||||
cdrField.Width = 24
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
default:
|
||||
cdrField.Mandatory = false
|
||||
if fixedWidth {
|
||||
cdrField.Width = 30
|
||||
cdrField.Strip = "xright"
|
||||
cdrField.Padding = "left"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
483
config/cdreconfig_test.go
Normal file
483
config/cdreconfig_test.go
Normal file
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCdreCfgNewCdreCdrFieldsFromIds(t *testing.T) {
|
||||
expectedFlds := []*CdreCdrField{
|
||||
&CdreCdrField{
|
||||
Name: utils.CGRID,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.CGRID,
|
||||
Width: 40,
|
||||
Strip: "",
|
||||
Padding: "",
|
||||
Layout: "",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: "extra1",
|
||||
Type: utils.CDRFIELD,
|
||||
Value: "extra1",
|
||||
Width: 30,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Layout: "",
|
||||
Mandatory: false,
|
||||
valueAsRsrField: &utils.RSRField{Id: "extra1"},
|
||||
},
|
||||
}
|
||||
if cdreFlds, err := NewCdreCdrFieldsFromIds(true, utils.CGRID, "extra1"); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(expectedFlds, cdreFlds) {
|
||||
t.Errorf("Expected: %v, received: %v", expectedFlds, cdreFlds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdreCfgValueAsRSRField(t *testing.T) {
|
||||
cdreCdrFld := &CdreCdrField{
|
||||
Name: utils.CGRID,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.CGRID,
|
||||
Width: 10,
|
||||
Strip: "xright",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
|
||||
}
|
||||
if rsrVal := cdreCdrFld.ValueAsRSRField(); rsrVal != cdreCdrFld.valueAsRsrField {
|
||||
t.Error("Unexpected value received: ", rsrVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdreCfgNewDefaultCdreConfig(t *testing.T) {
|
||||
eCdreCfg := new(CdreConfig)
|
||||
eCdreCfg.CdrFormat = utils.CSV
|
||||
eCdreCfg.FieldSeparator = utils.CSV_SEP
|
||||
eCdreCfg.DataUsageMultiplyFactor = 0.0
|
||||
eCdreCfg.CostMultiplyFactor = 0.0
|
||||
eCdreCfg.CostRoundingDecimals = -1
|
||||
eCdreCfg.CostShiftDigits = 0
|
||||
eCdreCfg.MaskDestId = ""
|
||||
eCdreCfg.MaskLength = 0
|
||||
eCdreCfg.ExportDir = "/var/log/cgrates/cdre"
|
||||
eCdreCfg.ContentFields = []*CdreCdrField{
|
||||
&CdreCdrField{
|
||||
Name: utils.CGRID,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.CGRID,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.MEDI_RUNID,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.MEDI_RUNID,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.TOR,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.TOR,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.ACCID,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.ACCID,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.REQTYPE,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.REQTYPE,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.DIRECTION,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.DIRECTION,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.TENANT,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.TENANT,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.CATEGORY,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.CATEGORY,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.ACCOUNT,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.ACCOUNT,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.SUBJECT,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.SUBJECT,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.DESTINATION,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.DESTINATION,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.SETUP_TIME,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.SETUP_TIME,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.ANSWER_TIME,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.ANSWER_TIME,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.USAGE,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.USAGE,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: utils.COST,
|
||||
Type: utils.CDRFIELD,
|
||||
Value: utils.COST,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.COST},
|
||||
},
|
||||
}
|
||||
if cdreCfg, err := NewDefaultCdreConfig(); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCfg, cdreCfg) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCfg, cdreCfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdreCfgSetDefaultFieldProperties(t *testing.T) {
|
||||
cdreCdrFld := &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
|
||||
}
|
||||
eCdreCdrFld := &CdreCdrField{
|
||||
Width: 40,
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CGRID},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ORDERID},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 11,
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ORDERID},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 6,
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.TOR},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 36,
|
||||
Strip: "left",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ACCID},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 15,
|
||||
Strip: "left",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CDRHOST},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 15,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CDRSOURCE},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 13,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.REQTYPE},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 4,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.DIRECTION},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 24,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.TENANT},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 10,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.CATEGORY},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 24,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ACCOUNT},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 24,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.SUBJECT},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 24,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.DESTINATION},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 30,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Layout: "2006-01-02T15:04:05Z07:00",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.SETUP_TIME},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 30,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Layout: "2006-01-02T15:04:05Z07:00",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.ANSWER_TIME},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 30,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.USAGE},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 20,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.MEDI_RUNID},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.COST},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 24,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: true,
|
||||
valueAsRsrField: &utils.RSRField{Id: utils.COST},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
cdreCdrFld = &CdreCdrField{
|
||||
valueAsRsrField: &utils.RSRField{Id: "extra_1"},
|
||||
}
|
||||
eCdreCdrFld = &CdreCdrField{
|
||||
Width: 30,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Mandatory: false,
|
||||
valueAsRsrField: &utils.RSRField{Id: "extra_1"},
|
||||
}
|
||||
if err := cdreCdrFld.setDefaultFieldProperties(true); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eCdreCdrFld, cdreCdrFld) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCdrFld, cdreCdrFld)
|
||||
}
|
||||
}
|
||||
207
config/cdrstatsconfig.go
Normal file
207
config/cdrstatsconfig.go
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"code.google.com/p/goconf/conf"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Parse the configuration file for CDRStatConfigs
|
||||
func ParseCfgDefaultCDRStatsConfig(c *conf.ConfigFile) (*CdrStatsConfig, error) {
|
||||
var err error
|
||||
csCfg := NewCdrStatsConfigWithDefaults()
|
||||
if hasOpt := c.HasOption("cdrstats", "queue_length"); hasOpt {
|
||||
csCfg.QueueLength, _ = c.GetInt("cdrstats", "queue_length")
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "time_window"); hasOpt {
|
||||
durStr, _ := c.GetString("cdrstats", "time_window")
|
||||
if csCfg.TimeWindow, err = utils.ParseDurationWithSecs(durStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "metrics"); hasOpt {
|
||||
metricsStr, _ := c.GetString("cdrstats", "metrics")
|
||||
if csCfg.Metrics, err = ConfigSlice(metricsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "setup_interval"); hasOpt {
|
||||
setupIntervalStr, _ := c.GetString("cdrstats", "setup_interval")
|
||||
if len(setupIntervalStr) != 0 { // If we parse empty, will get empty time, we prefer nil
|
||||
if setupIntervalSlc, err := ConfigSlice(setupIntervalStr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, setupTimeStr := range setupIntervalSlc {
|
||||
if setupTime, err := utils.ParseTimeDetectLayout(setupTimeStr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
csCfg.SetupInterval = append(csCfg.SetupInterval, setupTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "tors"); hasOpt {
|
||||
torsStr, _ := c.GetString("cdrstats", "tors")
|
||||
if csCfg.TORs, err = ConfigSlice(torsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "cdr_hosts"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "cdr_hosts")
|
||||
if csCfg.CdrHosts, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "cdr_sources"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "cdr_sources")
|
||||
if csCfg.CdrSources, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "req_types"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "req_types")
|
||||
if csCfg.ReqTypes, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "directions"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "directions")
|
||||
if csCfg.Directions, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "tenants"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "tenants")
|
||||
if csCfg.Tenants, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "categories"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "categories")
|
||||
if csCfg.Categories, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "accounts"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "accounts")
|
||||
if csCfg.Accounts, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "subjects"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "subjects")
|
||||
if csCfg.Subjects, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "destination_prefixes"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "destination_prefixes")
|
||||
if csCfg.DestinationPrefixes, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "usage_interval"); hasOpt {
|
||||
usageIntervalStr, _ := c.GetString("cdrstats", "usage_interval")
|
||||
if usageIntervalSlc, err := ConfigSlice(usageIntervalStr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, usageDurStr := range usageIntervalSlc {
|
||||
if usageDur, err := utils.ParseDurationWithSecs(usageDurStr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
csCfg.UsageInterval = append(csCfg.UsageInterval, usageDur)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "mediation_run_ids"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "mediation_run_ids")
|
||||
if csCfg.MediationRunIds, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "rated_accounts"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "rated_accounts")
|
||||
if csCfg.RatedAccounts, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "rated_subjects"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "rated_subjects")
|
||||
if csCfg.RatedSubjects, err = ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt := c.HasOption("cdrstats", "cost_intervals"); hasOpt {
|
||||
valsStr, _ := c.GetString("cdrstats", "cost_intervals")
|
||||
if costSlc, err := ConfigSlice(valsStr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, costStr := range costSlc {
|
||||
if cost, err := strconv.ParseFloat(costStr, 64); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
csCfg.CostInterval = append(csCfg.CostInterval, cost)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return csCfg, nil
|
||||
}
|
||||
|
||||
func NewCdrStatsConfigWithDefaults() *CdrStatsConfig {
|
||||
csCfg := new(CdrStatsConfig)
|
||||
csCfg.setDefaults()
|
||||
return csCfg
|
||||
}
|
||||
|
||||
type CdrStatsConfig struct {
|
||||
Id string // Config id, unique per config instance
|
||||
QueueLength int // Number of items in the stats buffer
|
||||
TimeWindow time.Duration // Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
|
||||
Metrics []string // ASR, ACD, ACC
|
||||
SetupInterval []time.Time // 2 or less items (>= start interval,< stop_interval)
|
||||
TORs []string
|
||||
CdrHosts []string
|
||||
CdrSources []string
|
||||
ReqTypes []string
|
||||
Directions []string
|
||||
Tenants []string
|
||||
Categories []string
|
||||
Accounts []string
|
||||
Subjects []string
|
||||
DestinationPrefixes []string
|
||||
UsageInterval []time.Duration // 2 or less items (>= Usage, <Usage)
|
||||
MediationRunIds []string
|
||||
RatedAccounts []string
|
||||
RatedSubjects []string
|
||||
CostInterval []float64 // 2 or less items, (>=Cost, <Cost)
|
||||
}
|
||||
|
||||
func (csCfg *CdrStatsConfig) setDefaults() {
|
||||
csCfg.Id = utils.META_DEFAULT
|
||||
csCfg.QueueLength = 50
|
||||
csCfg.TimeWindow = time.Duration(1) * time.Hour
|
||||
csCfg.Metrics = []string{"ASR", "ACD", "ACC"}
|
||||
}
|
||||
693
config/config.go
693
config/config.go
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -54,109 +55,93 @@ func SetCgrConfig(cfg *CGRConfig) {
|
||||
|
||||
// Holds system configuration, defaults are overwritten with values from config file if found
|
||||
type CGRConfig struct {
|
||||
RatingDBType string
|
||||
RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
RatingDBPort string // The port to bind to.
|
||||
RatingDBName string // The name of the database to connect to.
|
||||
RatingDBUser string // The user to sign in as.
|
||||
RatingDBPass string // The user's password.
|
||||
AccountDBType string
|
||||
AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
AccountDBPort string // The port to bind to.
|
||||
AccountDBName string // The name of the database to connect to.
|
||||
AccountDBUser string // The user to sign in as.
|
||||
AccountDBPass string // The user's password.
|
||||
StorDBType string // Should reflect the database type used to store logs
|
||||
StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
StorDBPort string // Th e port to bind to.
|
||||
StorDBName string // The name of the database to connect to.
|
||||
StorDBUser string // The user to sign in as.
|
||||
StorDBPass string // The user's password.
|
||||
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
|
||||
RPCJSONListen string // RPC JSON listening address
|
||||
RPCGOBListen string // RPC GOB listening address
|
||||
HTTPListen string // HTTP listening address
|
||||
DefaultReqType string // Use this request type if not defined on top
|
||||
DefaultTOR string // set default type of record
|
||||
DefaultTenant string // set default tenant
|
||||
DefaultSubject string // set default rating subject, useful in case of fallback
|
||||
RoundingMethod string // Rounding method for the end price: <*up|*middle|*down>
|
||||
RoundingDecimals int // Number of decimals to round end prices at
|
||||
XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
|
||||
RaterEnabled bool // start standalone server (no balancer)
|
||||
RaterBalancer string // balancer address host:port
|
||||
BalancerEnabled bool
|
||||
SchedulerEnabled bool
|
||||
CDRSEnabled bool // Enable CDR Server service
|
||||
CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
|
||||
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
CdreCdrFormat string // Format of the exported CDRs. <csv>
|
||||
CdreDir string // Path towards exported cdrs directory
|
||||
CdreExportedFields []*utils.RSRField // List of fields in the exported CDRs
|
||||
CdreFWXmlTemplate *CgrXmlCdreFwCfg // Use this configuration as export template in case of fixed fields length
|
||||
CdrcEnabled bool // Enable CDR client functionality
|
||||
CdrcCdrs string // Address where to reach CDR server
|
||||
CdrcCdrsMethod string // Mechanism to use when posting CDRs on server <http_cgr>
|
||||
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
|
||||
CdrcCdrType string // CDR file format <csv>.
|
||||
CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
|
||||
CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
|
||||
CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
|
||||
CdrcAccIdField string // Accounting id field identifier. Use index number in case of .csv cdrs.
|
||||
CdrcReqTypeField string // Request type field identifier. Use index number in case of .csv cdrs.
|
||||
CdrcDirectionField string // Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcTenantField string // Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcTorField string // Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcAccountField string // Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcSubjectField string // Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
CdrcDestinationField string // Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcSetupTimeField string // Setup time field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcAnswerTimeField string // Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcDurationField string // Duration field identifier. Use index numbers in case of .csv cdrs.
|
||||
CdrcExtraFields []string // Extra fields to extract, special format in case of .csv "field1:index1,field2:index2"
|
||||
SMEnabled bool
|
||||
SMSwitchType string
|
||||
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
|
||||
SMRaterReconnects int // Number of reconnect attempts to rater
|
||||
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
|
||||
SMMaxCallDuration time.Duration // The maximum duration of a call
|
||||
SMRunIds []string // Identifiers of additional sessions control.
|
||||
SMReqTypeFields []string // Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMDirectionFields []string // Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMTenantFields []string // Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMTORFields []string // Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMAccountFields []string // Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMSubjectFields []string // Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMDestFields []string // Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMSetupTimeFields []string // Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMAnswerTimeFields []string // Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
SMDurationFields []string // Name of duration fields to be used during additional sessions control <""|*default|field_name>.
|
||||
MediatorEnabled bool // Starts Mediator service: <true|false>.
|
||||
MediatorRater string // Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
MediatorRaterReconnects int // Number of reconnects to rater before giving up.
|
||||
MediatorRunIds []string // Identifiers for each mediation run on CDRs
|
||||
MediatorReqTypeFields []string // Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
|
||||
MediatorDirectionFields []string // Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorTenantFields []string // Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorTORFields []string // Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorAccountFields []string // Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorSubjectFields []string // Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorDestFields []string // Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorSetupTimeFields []string // Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorAnswerTimeFields []string // Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
MediatorDurationFields []string // Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
FreeswitchServer string // freeswitch address host:port
|
||||
FreeswitchPass string // FS socket password
|
||||
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
|
||||
HistoryAgentEnabled bool // Starts History as an agent: <true|false>.
|
||||
HistoryServer string // Address where to reach the master history server: <internal|x.y.z.y:1234>
|
||||
HistoryServerEnabled bool // Starts History as server: <true|false>.
|
||||
HistoryDir string // Location on disk where to store history files.
|
||||
HistorySaveInterval time.Duration // The timout duration between history writes
|
||||
MailerServer string // The server to use when sending emails out
|
||||
MailerAuthUser string // Authenticate to email server using this user
|
||||
MailerAuthPass string // Authenticate to email server with this password
|
||||
MailerFromAddr string // From address used when sending emails out
|
||||
RatingDBType string
|
||||
RatingDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
RatingDBPort string // The port to bind to.
|
||||
RatingDBName string // The name of the database to connect to.
|
||||
RatingDBUser string // The user to sign in as.
|
||||
RatingDBPass string // The user's password.
|
||||
AccountDBType string
|
||||
AccountDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
AccountDBPort string // The port to bind to.
|
||||
AccountDBName string // The name of the database to connect to.
|
||||
AccountDBUser string // The user to sign in as.
|
||||
AccountDBPass string // The user's password.
|
||||
StorDBType string // Should reflect the database type used to store logs
|
||||
StorDBHost string // The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
StorDBPort string // Th e port to bind to.
|
||||
StorDBName string // The name of the database to connect to.
|
||||
StorDBUser string // The user to sign in as.
|
||||
StorDBPass string // The user's password.
|
||||
DBDataEncoding string // The encoding used to store object data in strings: <msgpack|json>
|
||||
RPCJSONListen string // RPC JSON listening address
|
||||
RPCGOBListen string // RPC GOB listening address
|
||||
HTTPListen string // HTTP listening address
|
||||
DefaultReqType string // Use this request type if not defined on top
|
||||
DefaultCategory string // set default type of record
|
||||
DefaultTenant string // set default tenant
|
||||
DefaultSubject string // set default rating subject, useful in case of fallback
|
||||
RoundingDecimals int // Number of decimals to round end prices at
|
||||
HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate
|
||||
XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document
|
||||
RaterEnabled bool // start standalone server (no balancer)
|
||||
RaterBalancer string // balancer address host:port
|
||||
BalancerEnabled bool
|
||||
SchedulerEnabled bool
|
||||
CDRSEnabled bool // Enable CDR Server service
|
||||
CDRSExtraFields []*utils.RSRField // Extra fields to store in CDRs
|
||||
CDRSMediator string // Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
CDRSStats string // Address where to reach the Mediator. <""|intenal>
|
||||
CDRSStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
|
||||
CDRStatsEnabled bool // Enable CDR Stats service
|
||||
CDRStatConfig *CdrStatsConfig // Active cdr stats configuration instances
|
||||
CdreDefaultInstance *CdreConfig // Will be used in the case no specific one selected by API
|
||||
CdrcEnabled bool // Enable CDR client functionality
|
||||
CdrcCdrs string // Address where to reach CDR server
|
||||
CdrcRunDelay time.Duration // Sleep interval between consecutive runs, 0 to use automation via inotify
|
||||
CdrcCdrType string // CDR file format <csv>.
|
||||
CdrcCsvSep string // Separator used in case of csv files. One character only supported.
|
||||
CdrcCdrInDir string // Absolute path towards the directory where the CDRs are stored.
|
||||
CdrcCdrOutDir string // Absolute path towards the directory where processed CDRs will be moved.
|
||||
CdrcSourceId string // Tag identifying the source of the CDRs within CGRS database.
|
||||
CdrcCdrFields map[string][]*utils.RSRField // FieldName/RSRField format. Index number in case of .csv cdrs.
|
||||
SMEnabled bool
|
||||
SMSwitchType string
|
||||
SMRater string // address where to access rater. Can be internal, direct rater address or the address of a balancer
|
||||
SMCdrS string //
|
||||
SMReconnects int // Number of reconnect attempts to rater
|
||||
SMDebitInterval int // the period to be debited in advanced during a call (in seconds)
|
||||
SMMaxCallDuration time.Duration // The maximum duration of a call
|
||||
SMMinCallDuration time.Duration // Only authorize calls with allowed duration bigger than this
|
||||
MediatorEnabled bool // Starts Mediator service: <true|false>.
|
||||
MediatorReconnects int // Number of reconnects to rater before giving up.
|
||||
MediatorRater string
|
||||
MediatorStats string // Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
MediatorStoreDisable bool // When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
|
||||
DerivedChargers utils.DerivedChargers // System wide derived chargers, added to the account level ones
|
||||
CombinedDerivedChargers bool // Combine accounts specific derived_chargers with server configured
|
||||
FreeswitchServer string // freeswitch address host:port
|
||||
FreeswitchPass string // FS socket password
|
||||
FreeswitchReconnects int // number of times to attempt reconnect after connect fails
|
||||
FSMinDurLowBalance time.Duration // Threshold which will trigger low balance warnings
|
||||
FSLowBalanceAnnFile string // File to be played when low balance is reached
|
||||
FSEmptyBalanceContext string // If defined, call will be transfered to this context on empty balance
|
||||
FSEmptyBalanceAnnFile string // File to be played before disconnecting prepaid calls (applies only if no context defined)
|
||||
FSCdrExtraFields []*utils.RSRField // Extra fields to store in CDRs in case of processing them
|
||||
OsipsListenUdp string // Address where to listen for event datagrams coming from OpenSIPS
|
||||
OsipsMiAddr string // Adress where to reach OpenSIPS mi_datagram module
|
||||
OsipsEvSubscInterval time.Duration // Refresh event subscription at this interval
|
||||
OsipsReconnects int // Number of attempts on connect failure.
|
||||
HistoryAgentEnabled bool // Starts History as an agent: <true|false>.
|
||||
HistoryServer string // Address where to reach the master history server: <internal|x.y.z.y:1234>
|
||||
HistoryServerEnabled bool // Starts History as server: <true|false>.
|
||||
HistoryDir string // Location on disk where to store history files.
|
||||
HistorySaveInterval time.Duration // The timout duration between history writes
|
||||
MailerServer string // The server to use when sending emails out
|
||||
MailerAuthUser string // Authenticate to email server using this user
|
||||
MailerAuthPass string // Authenticate to email server with this password
|
||||
MailerFromAddr string // From address used when sending emails out
|
||||
DataFolderPath string // Path towards data folder, for tests internal usage, not loading out of .cfg options
|
||||
}
|
||||
|
||||
func (self *CGRConfig) setDefaults() error {
|
||||
@@ -183,11 +168,11 @@ func (self *CGRConfig) setDefaults() error {
|
||||
self.RPCGOBListen = "127.0.0.1:2013"
|
||||
self.HTTPListen = "127.0.0.1:2080"
|
||||
self.DefaultReqType = utils.RATED
|
||||
self.DefaultTOR = "call"
|
||||
self.DefaultCategory = "call"
|
||||
self.DefaultTenant = "cgrates.org"
|
||||
self.DefaultSubject = "cgrates"
|
||||
self.RoundingMethod = utils.ROUNDING_MIDDLE
|
||||
self.RoundingDecimals = 4
|
||||
self.RoundingDecimals = 10
|
||||
self.HttpSkipTlsVerify = false
|
||||
self.XmlCfgDocument = nil
|
||||
self.RaterEnabled = false
|
||||
self.RaterBalancer = ""
|
||||
@@ -196,125 +181,87 @@ func (self *CGRConfig) setDefaults() error {
|
||||
self.CDRSEnabled = false
|
||||
self.CDRSExtraFields = []*utils.RSRField{}
|
||||
self.CDRSMediator = ""
|
||||
self.CdreCdrFormat = "csv"
|
||||
self.CdreDir = "/var/log/cgrates/cdr/cdrexport/csv"
|
||||
self.CDRSStats = ""
|
||||
self.CDRSStoreDisable = false
|
||||
self.CDRStatsEnabled = false
|
||||
self.CDRStatConfig = NewCdrStatsConfigWithDefaults()
|
||||
self.CdreDefaultInstance, _ = NewDefaultCdreConfig()
|
||||
self.CdrcEnabled = false
|
||||
self.CdrcCdrs = utils.INTERNAL
|
||||
self.CdrcCdrsMethod = "http_cgr"
|
||||
self.CdrcRunDelay = time.Duration(0)
|
||||
self.CdrcCdrType = "csv"
|
||||
self.CdrcCdrInDir = "/var/log/cgrates/cdr/cdrc/in"
|
||||
self.CdrcCdrOutDir = "/var/log/cgrates/cdr/cdrc/out"
|
||||
self.CdrcSourceId = "freeswitch_csv"
|
||||
self.CdrcAccIdField = "0"
|
||||
self.CdrcReqTypeField = "1"
|
||||
self.CdrcDirectionField = "2"
|
||||
self.CdrcTenantField = "3"
|
||||
self.CdrcTorField = "4"
|
||||
self.CdrcAccountField = "5"
|
||||
self.CdrcSubjectField = "6"
|
||||
self.CdrcDestinationField = "7"
|
||||
self.CdrcSetupTimeField = "8"
|
||||
self.CdrcAnswerTimeField = "9"
|
||||
self.CdrcDurationField = "10"
|
||||
self.CdrcExtraFields = []string{}
|
||||
self.CdrcCdrType = utils.CSV
|
||||
self.CdrcCsvSep = string(utils.CSV_SEP)
|
||||
self.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
|
||||
self.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
|
||||
self.CdrcSourceId = "csv"
|
||||
self.CdrcCdrFields = map[string][]*utils.RSRField{
|
||||
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
|
||||
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
|
||||
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
|
||||
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
|
||||
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
|
||||
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
|
||||
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
|
||||
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
|
||||
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
|
||||
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
|
||||
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
|
||||
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
|
||||
}
|
||||
self.MediatorEnabled = false
|
||||
self.MediatorRater = "internal"
|
||||
self.MediatorRaterReconnects = 3
|
||||
self.MediatorRunIds = []string{}
|
||||
self.MediatorSubjectFields = []string{}
|
||||
self.MediatorReqTypeFields = []string{}
|
||||
self.MediatorDirectionFields = []string{}
|
||||
self.MediatorTenantFields = []string{}
|
||||
self.MediatorTORFields = []string{}
|
||||
self.MediatorAccountFields = []string{}
|
||||
self.MediatorDestFields = []string{}
|
||||
self.MediatorSetupTimeFields = []string{}
|
||||
self.MediatorAnswerTimeFields = []string{}
|
||||
self.MediatorDurationFields = []string{}
|
||||
self.MediatorRater = utils.INTERNAL
|
||||
self.MediatorReconnects = 3
|
||||
self.MediatorStats = utils.INTERNAL
|
||||
self.MediatorStoreDisable = false
|
||||
self.DerivedChargers = make(utils.DerivedChargers, 0)
|
||||
self.CombinedDerivedChargers = true
|
||||
self.SMEnabled = false
|
||||
self.SMSwitchType = FS
|
||||
self.SMRater = "internal"
|
||||
self.SMRaterReconnects = 3
|
||||
self.SMRater = utils.INTERNAL
|
||||
self.SMCdrS = ""
|
||||
self.SMReconnects = 3
|
||||
self.SMDebitInterval = 10
|
||||
self.SMMaxCallDuration = time.Duration(3) * time.Hour
|
||||
self.SMRunIds = []string{}
|
||||
self.SMReqTypeFields = []string{}
|
||||
self.SMDirectionFields = []string{}
|
||||
self.SMTenantFields = []string{}
|
||||
self.SMTORFields = []string{}
|
||||
self.SMAccountFields = []string{}
|
||||
self.SMSubjectFields = []string{}
|
||||
self.SMDestFields = []string{}
|
||||
self.SMSetupTimeFields = []string{}
|
||||
self.SMAnswerTimeFields = []string{}
|
||||
self.SMDurationFields = []string{}
|
||||
self.SMMinCallDuration = time.Duration(0)
|
||||
self.FreeswitchServer = "127.0.0.1:8021"
|
||||
self.FreeswitchPass = "ClueCon"
|
||||
self.FreeswitchReconnects = 5
|
||||
self.FSMinDurLowBalance = time.Duration(5) * time.Second
|
||||
self.FSLowBalanceAnnFile = ""
|
||||
self.FSEmptyBalanceContext = ""
|
||||
self.FSEmptyBalanceAnnFile = ""
|
||||
self.FSCdrExtraFields = []*utils.RSRField{}
|
||||
self.OsipsListenUdp = "127.0.0.1:2020"
|
||||
self.OsipsMiAddr = "127.0.0.1:8020"
|
||||
self.OsipsEvSubscInterval = time.Duration(60) * time.Second
|
||||
self.OsipsReconnects = 3
|
||||
self.HistoryAgentEnabled = false
|
||||
self.HistoryServerEnabled = false
|
||||
self.HistoryServer = "internal"
|
||||
self.HistoryServer = utils.INTERNAL
|
||||
self.HistoryDir = "/var/log/cgrates/history"
|
||||
self.HistorySaveInterval = time.Duration(1) * time.Second
|
||||
self.MailerServer = "localhost:25"
|
||||
self.MailerAuthUser = "cgrates"
|
||||
self.MailerAuthPass = "CGRateS.org"
|
||||
self.MailerFromAddr = "cgr-mailer@localhost.localdomain"
|
||||
self.CdreExportedFields = []*utils.RSRField{
|
||||
&utils.RSRField{Id: utils.CGRID},
|
||||
&utils.RSRField{Id: utils.MEDI_RUNID},
|
||||
&utils.RSRField{Id: utils.ACCID},
|
||||
&utils.RSRField{Id: utils.CDRHOST},
|
||||
&utils.RSRField{Id: utils.REQTYPE},
|
||||
&utils.RSRField{Id: utils.DIRECTION},
|
||||
&utils.RSRField{Id: utils.TENANT},
|
||||
&utils.RSRField{Id: utils.TOR},
|
||||
&utils.RSRField{Id: utils.ACCOUNT},
|
||||
&utils.RSRField{Id: utils.SUBJECT},
|
||||
&utils.RSRField{Id: utils.DESTINATION},
|
||||
&utils.RSRField{Id: utils.SETUP_TIME},
|
||||
&utils.RSRField{Id: utils.ANSWER_TIME},
|
||||
&utils.RSRField{Id: utils.DURATION},
|
||||
&utils.RSRField{Id: utils.COST},
|
||||
}
|
||||
self.DataFolderPath = "/usr/share/cgrates/"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CGRConfig) checkConfigSanity() error {
|
||||
// Cdre sanity check for fixed_width
|
||||
if self.CdreCdrFormat == utils.CDRE_FIXED_WIDTH {
|
||||
if self.XmlCfgDocument == nil {
|
||||
return errors.New("Need XmlConfigurationDocument for fixed_width cdr export")
|
||||
} else if self.CdreFWXmlTemplate == nil {
|
||||
return errors.New("Need XmlTemplate for fixed_width cdr export")
|
||||
if self.CdrcEnabled {
|
||||
if len(self.CdrcCdrFields) == 0 {
|
||||
return errors.New("CdrC enabled but no fields to be processed defined!")
|
||||
}
|
||||
if self.CdrcCdrType == utils.CSV {
|
||||
for _, rsrFldLst := range self.CdrcCdrFields {
|
||||
for _, rsrFld := range rsrFldLst {
|
||||
if _, errConv := strconv.Atoi(rsrFld.Id); errConv != nil {
|
||||
return fmt.Errorf("CDR fields must be indices in case of .csv files, have instead: %s", rsrFld.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SessionManager should have same fields config length for session emulation
|
||||
if len(self.SMReqTypeFields) != len(self.SMRunIds) ||
|
||||
len(self.SMDirectionFields) != len(self.SMRunIds) ||
|
||||
len(self.SMTenantFields) != len(self.SMRunIds) ||
|
||||
len(self.SMTORFields) != len(self.SMRunIds) ||
|
||||
len(self.SMAccountFields) != len(self.SMRunIds) ||
|
||||
len(self.SMSubjectFields) != len(self.SMRunIds) ||
|
||||
len(self.SMDestFields) != len(self.SMRunIds) ||
|
||||
len(self.SMSetupTimeFields) != len(self.SMRunIds) ||
|
||||
len(self.SMAnswerTimeFields) != len(self.SMRunIds) ||
|
||||
len(self.SMDurationFields) != len(self.SMRunIds) {
|
||||
return errors.New("<ConfigSanity> Inconsistent fields length for SessionManager session emulation")
|
||||
}
|
||||
// Mediator needs to have consistent extra fields definition
|
||||
if len(self.MediatorReqTypeFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorDirectionFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorTenantFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorTORFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorAccountFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorSubjectFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorDestFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorSetupTimeFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorAnswerTimeFields) != len(self.MediatorRunIds) ||
|
||||
len(self.MediatorDurationFields) != len(self.MediatorRunIds) {
|
||||
return errors.New("<ConfigSanity> Inconsistent fields length for Mediator extra fields")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -328,12 +275,8 @@ func NewDefaultCGRConfig() (*CGRConfig, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Instantiate a new CGRConfig setting defaults or reading from file
|
||||
func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
|
||||
c, err := conf.ReadConfigFile(*cfgPath)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
|
||||
}
|
||||
// Unifies the config handling for both tests and real path
|
||||
func NewCGRConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
cfg, err := loadConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -344,26 +287,28 @@ func NewCGRConfig(cfgPath *string) (*CGRConfig, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func NewCGRConfigBytes(data []byte) (*CGRConfig, error) {
|
||||
// Instantiate a new CGRConfig setting defaults or reading from file
|
||||
func NewCGRConfigFromFile(cfgPath *string) (*CGRConfig, error) {
|
||||
c, err := conf.ReadConfigFile(*cfgPath)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
|
||||
}
|
||||
return NewCGRConfig(c)
|
||||
}
|
||||
|
||||
func NewCGRConfigFromBytes(data []byte) (*CGRConfig, error) {
|
||||
c, err := conf.ReadConfigBytes(data)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Could not open the configuration file: %s", err))
|
||||
}
|
||||
cfg, err := loadConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cfg.checkConfigSanity(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
return NewCGRConfig(c)
|
||||
}
|
||||
|
||||
func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
cfg := &CGRConfig{}
|
||||
cfg.setDefaults()
|
||||
var hasOpt bool
|
||||
var errParse error
|
||||
var err error
|
||||
if hasOpt = c.HasOption("global", "ratingdb_type"); hasOpt {
|
||||
cfg.RatingDBType, _ = c.GetString("global", "ratingdb_type")
|
||||
}
|
||||
@@ -433,8 +378,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("global", "default_reqtype"); hasOpt {
|
||||
cfg.DefaultReqType, _ = c.GetString("global", "default_reqtype")
|
||||
}
|
||||
if hasOpt = c.HasOption("global", "default_tor"); hasOpt {
|
||||
cfg.DefaultTOR, _ = c.GetString("global", "default_tor")
|
||||
if hasOpt = c.HasOption("global", "default_category"); hasOpt {
|
||||
cfg.DefaultCategory, _ = c.GetString("global", "default_category")
|
||||
}
|
||||
if hasOpt = c.HasOption("global", "default_tenant"); hasOpt {
|
||||
cfg.DefaultTenant, _ = c.GetString("global", "default_tenant")
|
||||
@@ -442,12 +387,12 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("global", "default_subject"); hasOpt {
|
||||
cfg.DefaultSubject, _ = c.GetString("global", "default_subject")
|
||||
}
|
||||
if hasOpt = c.HasOption("global", "rounding_method"); hasOpt {
|
||||
cfg.RoundingMethod, _ = c.GetString("global", "rounding_method")
|
||||
}
|
||||
if hasOpt = c.HasOption("global", "rounding_decimals"); hasOpt {
|
||||
cfg.RoundingDecimals, _ = c.GetInt("global", "rounding_decimals")
|
||||
}
|
||||
if hasOpt = c.HasOption("global", "http_skip_tls_veify"); hasOpt {
|
||||
cfg.HttpSkipTlsVerify, _ = c.GetBool("global", "http_skip_tls_veify")
|
||||
}
|
||||
// XML config path defined, try loading the document
|
||||
if hasOpt = c.HasOption("global", "xmlcfg_path"); hasOpt {
|
||||
xmlCfgPath, _ := c.GetString("global", "xmlcfg_path")
|
||||
@@ -478,8 +423,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrs", "extra_fields"); hasOpt {
|
||||
extraFieldsStr, _ := c.GetString("cdrs", "extra_fields")
|
||||
if extraFields, err := ParseRSRFields(extraFieldsStr); err != nil {
|
||||
return nil, errParse
|
||||
if extraFields, err := utils.ParseRSRFields(extraFieldsStr, utils.FIELDS_SEP); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cfg.CDRSExtraFields = extraFields
|
||||
}
|
||||
@@ -487,27 +432,56 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("cdrs", "mediator"); hasOpt {
|
||||
cfg.CDRSMediator, _ = c.GetString("cdrs", "mediator")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrs", "cdrstats"); hasOpt {
|
||||
cfg.CDRSStats, _ = c.GetString("cdrs", "cdrstats")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrs", "store_disable"); hasOpt {
|
||||
cfg.CDRSStoreDisable, _ = c.GetBool("cdrs", "store_disable")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrstats", "enabled"); hasOpt {
|
||||
cfg.CDRStatsEnabled, _ = c.GetBool("cdrstats", "enabled")
|
||||
}
|
||||
if cfg.CDRStatConfig, err = ParseCfgDefaultCDRStatsConfig(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "cdr_format"); hasOpt {
|
||||
cfg.CdreCdrFormat, _ = c.GetString("cdre", "cdr_format")
|
||||
cfg.CdreDefaultInstance.CdrFormat, _ = c.GetString("cdre", "cdr_format")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "mask_destination_id"); hasOpt {
|
||||
cfg.CdreDefaultInstance.MaskDestId, _ = c.GetString("cdre", "mask_destination_id")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "mask_length"); hasOpt {
|
||||
cfg.CdreDefaultInstance.MaskLength, _ = c.GetInt("cdre", "mask_length")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "data_usage_multiply_factor"); hasOpt {
|
||||
cfg.CdreDefaultInstance.DataUsageMultiplyFactor, _ = c.GetFloat64("cdre", "data_usage_multiply_factor")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "cost_multiply_factor"); hasOpt {
|
||||
cfg.CdreDefaultInstance.CostMultiplyFactor, _ = c.GetFloat64("cdre", "cost_multiply_factor")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "cost_rounding_decimals"); hasOpt {
|
||||
cfg.CdreDefaultInstance.CostRoundingDecimals, _ = c.GetInt("cdre", "cost_rounding_decimals")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "cost_shift_digits"); hasOpt {
|
||||
cfg.CdreDefaultInstance.CostShiftDigits, _ = c.GetInt("cdre", "cost_shift_digits")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "export_template"); hasOpt { // Load configs for csv normally from template, fixed_width from xml file
|
||||
exportTemplate, _ := c.GetString("cdre", "export_template")
|
||||
if cfg.CdreCdrFormat != utils.CDRE_FIXED_WIDTH { // Csv most likely
|
||||
if extraFields, err := ParseRSRFields(exportTemplate); err != nil {
|
||||
return nil, errParse
|
||||
} else {
|
||||
cfg.CdreExportedFields = extraFields
|
||||
if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
|
||||
if xmlTemplates := cfg.XmlCfgDocument.GetCdreCfgs(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); xmlTemplates != nil {
|
||||
cfg.CdreDefaultInstance = xmlTemplates[exportTemplate[len(utils.XML_PROFILE_PREFIX):]].AsCdreConfig()
|
||||
}
|
||||
} else if strings.HasPrefix(exportTemplate, utils.XML_PROFILE_PREFIX) {
|
||||
if xmlTemplate, err := cfg.XmlCfgDocument.GetCdreFWCfg(exportTemplate[len(utils.XML_PROFILE_PREFIX):]); err != nil {
|
||||
} else { // Not loading out of template
|
||||
if flds, err := NewCdreCdrFieldsFromIds(cfg.CdreDefaultInstance.CdrFormat == utils.CDRE_FIXED_WIDTH,
|
||||
strings.Split(exportTemplate, string(utils.CSV_SEP))...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cfg.CdreFWXmlTemplate = xmlTemplate
|
||||
cfg.CdreDefaultInstance.ContentFields = flds
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("cdre", "export_dir"); hasOpt {
|
||||
cfg.CdreDir, _ = c.GetString("cdre", "export_dir")
|
||||
cfg.CdreDefaultInstance.ExportDir, _ = c.GetString("cdre", "export_dir")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "enabled"); hasOpt {
|
||||
cfg.CdrcEnabled, _ = c.GetBool("cdrc", "enabled")
|
||||
@@ -515,18 +489,18 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("cdrc", "cdrs"); hasOpt {
|
||||
cfg.CdrcCdrs, _ = c.GetString("cdrc", "cdrs")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "cdrs_method"); hasOpt {
|
||||
cfg.CdrcCdrsMethod, _ = c.GetString("cdrc", "cdrs_method")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "run_delay"); hasOpt {
|
||||
durStr, _ := c.GetString("cdrc", "run_delay")
|
||||
if cfg.CdrcRunDelay, errParse = utils.ParseDurationWithSecs(durStr); errParse != nil {
|
||||
return nil, errParse
|
||||
if cfg.CdrcRunDelay, err = utils.ParseDurationWithSecs(durStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "cdr_type"); hasOpt {
|
||||
cfg.CdrcCdrType, _ = c.GetString("cdrc", "cdr_type")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "csv_separator"); hasOpt {
|
||||
cfg.CdrcCsvSep, _ = c.GetString("cdrc", "csv_separator")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "cdr_in_dir"); hasOpt {
|
||||
cfg.CdrcCdrInDir, _ = c.GetString("cdrc", "cdr_in_dir")
|
||||
}
|
||||
@@ -536,42 +510,26 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("cdrc", "cdr_source_id"); hasOpt {
|
||||
cfg.CdrcSourceId, _ = c.GetString("cdrc", "cdr_source_id")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "accid_field"); hasOpt {
|
||||
cfg.CdrcAccIdField, _ = c.GetString("cdrc", "accid_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "reqtype_field"); hasOpt {
|
||||
cfg.CdrcReqTypeField, _ = c.GetString("cdrc", "reqtype_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "direction_field"); hasOpt {
|
||||
cfg.CdrcDirectionField, _ = c.GetString("cdrc", "direction_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "tenant_field"); hasOpt {
|
||||
cfg.CdrcTenantField, _ = c.GetString("cdrc", "tenant_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "tor_field"); hasOpt {
|
||||
cfg.CdrcTorField, _ = c.GetString("cdrc", "tor_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "account_field"); hasOpt {
|
||||
cfg.CdrcAccountField, _ = c.GetString("cdrc", "account_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "subject_field"); hasOpt {
|
||||
cfg.CdrcSubjectField, _ = c.GetString("cdrc", "subject_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "destination_field"); hasOpt {
|
||||
cfg.CdrcDestinationField, _ = c.GetString("cdrc", "destination_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "setup_time_field"); hasOpt {
|
||||
cfg.CdrcSetupTimeField, _ = c.GetString("cdrc", "setup_time_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "answer_time_field"); hasOpt {
|
||||
cfg.CdrcAnswerTimeField, _ = c.GetString("cdrc", "answer_time_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "duration_field"); hasOpt {
|
||||
cfg.CdrcDurationField, _ = c.GetString("cdrc", "duration_field")
|
||||
}
|
||||
if hasOpt = c.HasOption("cdrc", "extra_fields"); hasOpt {
|
||||
if cfg.CdrcExtraFields, errParse = ConfigSlice(c, "cdrc", "extra_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
// ParseCdrcCdrFields
|
||||
torIdFld, _ := c.GetString("cdrc", "tor_field")
|
||||
accIdFld, _ := c.GetString("cdrc", "accid_field")
|
||||
reqtypeFld, _ := c.GetString("cdrc", "reqtype_field")
|
||||
directionFld, _ := c.GetString("cdrc", "direction_field")
|
||||
tenantFld, _ := c.GetString("cdrc", "tenant_field")
|
||||
categoryFld, _ := c.GetString("cdrc", "category_field")
|
||||
acntFld, _ := c.GetString("cdrc", "account_field")
|
||||
subjectFld, _ := c.GetString("cdrc", "subject_field")
|
||||
destFld, _ := c.GetString("cdrc", "destination_field")
|
||||
setupTimeFld, _ := c.GetString("cdrc", "setup_time_field")
|
||||
answerTimeFld, _ := c.GetString("cdrc", "answer_time_field")
|
||||
durFld, _ := c.GetString("cdrc", "usage_field")
|
||||
extraFlds, _ := c.GetString("cdrc", "extra_fields")
|
||||
if len(torIdFld) != 0 || len(accIdFld) != 0 || len(reqtypeFld) != 0 || len(directionFld) != 0 || len(tenantFld) != 0 || len(categoryFld) != 0 || len(acntFld) != 0 ||
|
||||
len(subjectFld) != 0 || len(destFld) != 0 || len(setupTimeFld) != 0 || len(answerTimeFld) != 0 || len(durFld) != 0 || len(extraFlds) != 0 {
|
||||
// We overwrite the defaults only if at least one of the fields were defined
|
||||
if cfg.CdrcCdrFields, err = ParseCdrcCdrFields(torIdFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
|
||||
setupTimeFld, answerTimeFld, durFld, extraFlds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "enabled"); hasOpt {
|
||||
@@ -580,63 +538,14 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("mediator", "rater"); hasOpt {
|
||||
cfg.MediatorRater, _ = c.GetString("mediator", "rater")
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "rater_reconnects"); hasOpt {
|
||||
cfg.MediatorRaterReconnects, _ = c.GetInt("mediator", "rater_reconnects")
|
||||
if hasOpt = c.HasOption("mediator", "reconnects"); hasOpt {
|
||||
cfg.MediatorReconnects, _ = c.GetInt("mediator", "reconnects")
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "run_ids"); hasOpt {
|
||||
if cfg.MediatorRunIds, errParse = ConfigSlice(c, "mediator", "run_ids"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "cdrstats"); hasOpt {
|
||||
cfg.MediatorStats, _ = c.GetString("mediator", "cdrstats")
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "subject_fields"); hasOpt {
|
||||
if cfg.MediatorSubjectFields, errParse = ConfigSlice(c, "mediator", "subject_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "reqtype_fields"); hasOpt {
|
||||
if cfg.MediatorReqTypeFields, errParse = ConfigSlice(c, "mediator", "reqtype_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "direction_fields"); hasOpt {
|
||||
if cfg.MediatorDirectionFields, errParse = ConfigSlice(c, "mediator", "direction_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "tenant_fields"); hasOpt {
|
||||
if cfg.MediatorTenantFields, errParse = ConfigSlice(c, "mediator", "tenant_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "tor_fields"); hasOpt {
|
||||
if cfg.MediatorTORFields, errParse = ConfigSlice(c, "mediator", "tor_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "account_fields"); hasOpt {
|
||||
if cfg.MediatorAccountFields, errParse = ConfigSlice(c, "mediator", "account_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "destination_fields"); hasOpt {
|
||||
if cfg.MediatorDestFields, errParse = ConfigSlice(c, "mediator", "destination_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "setup_time_fields"); hasOpt {
|
||||
if cfg.MediatorSetupTimeFields, errParse = ConfigSlice(c, "mediator", "setup_time_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "answer_time_fields"); hasOpt {
|
||||
if cfg.MediatorAnswerTimeFields, errParse = ConfigSlice(c, "mediator", "answer_time_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "duration_fields"); hasOpt {
|
||||
if cfg.MediatorDurationFields, errParse = ConfigSlice(c, "mediator", "duration_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
if hasOpt = c.HasOption("mediator", "store_disable"); hasOpt {
|
||||
cfg.MediatorStoreDisable, _ = c.GetBool("mediator", "store_disable")
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "enabled"); hasOpt {
|
||||
cfg.SMEnabled, _ = c.GetBool("session_manager", "enabled")
|
||||
@@ -647,71 +556,25 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("session_manager", "rater"); hasOpt {
|
||||
cfg.SMRater, _ = c.GetString("session_manager", "rater")
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "rater_reconnects"); hasOpt {
|
||||
cfg.SMRaterReconnects, _ = c.GetInt("session_manager", "rater_reconnects")
|
||||
if hasOpt = c.HasOption("session_manager", "cdrs"); hasOpt {
|
||||
cfg.SMCdrS, _ = c.GetString("session_manager", "cdrs")
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "reconnects"); hasOpt {
|
||||
cfg.SMReconnects, _ = c.GetInt("session_manager", "reconnects")
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "debit_interval"); hasOpt {
|
||||
cfg.SMDebitInterval, _ = c.GetInt("session_manager", "debit_interval")
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "min_call_duration"); hasOpt {
|
||||
minCallDurStr, _ := c.GetString("session_manager", "min_call_duration")
|
||||
if cfg.SMMinCallDuration, err = utils.ParseDurationWithSecs(minCallDurStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "max_call_duration"); hasOpt {
|
||||
maxCallDurStr, _ := c.GetString("session_manager", "max_call_duration")
|
||||
if cfg.SMMaxCallDuration, errParse = utils.ParseDurationWithSecs(maxCallDurStr); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "run_ids"); hasOpt {
|
||||
if cfg.SMRunIds, errParse = ConfigSlice(c, "session_manager", "run_ids"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "reqtype_fields"); hasOpt {
|
||||
if cfg.SMReqTypeFields, errParse = ConfigSlice(c, "session_manager", "reqtype_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "direction_fields"); hasOpt {
|
||||
if cfg.SMDirectionFields, errParse = ConfigSlice(c, "session_manager", "direction_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "tenant_fields"); hasOpt {
|
||||
if cfg.SMTenantFields, errParse = ConfigSlice(c, "session_manager", "tenant_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "tor_fields"); hasOpt {
|
||||
if cfg.SMTORFields, errParse = ConfigSlice(c, "session_manager", "tor_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "account_fields"); hasOpt {
|
||||
if cfg.SMAccountFields, errParse = ConfigSlice(c, "session_manager", "account_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "subject_fields"); hasOpt {
|
||||
if cfg.SMSubjectFields, errParse = ConfigSlice(c, "session_manager", "subject_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "destination_fields"); hasOpt {
|
||||
if cfg.SMDestFields, errParse = ConfigSlice(c, "session_manager", "destination_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "setup_time_fields"); hasOpt {
|
||||
if cfg.SMSetupTimeFields, errParse = ConfigSlice(c, "session_manager", "setup_time_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "answer_time_fields"); hasOpt {
|
||||
if cfg.SMAnswerTimeFields, errParse = ConfigSlice(c, "session_manager", "answer_time_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("session_manager", "duration_fields"); hasOpt {
|
||||
if cfg.SMDurationFields, errParse = ConfigSlice(c, "session_manager", "duration_fields"); errParse != nil {
|
||||
return nil, errParse
|
||||
if cfg.SMMaxCallDuration, err = utils.ParseDurationWithSecs(maxCallDurStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("freeswitch", "server"); hasOpt {
|
||||
@@ -723,6 +586,50 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
if hasOpt = c.HasOption("freeswitch", "reconnects"); hasOpt {
|
||||
cfg.FreeswitchReconnects, _ = c.GetInt("freeswitch", "reconnects")
|
||||
}
|
||||
if hasOpt = c.HasOption("freeswitch", "min_dur_low_balance"); hasOpt {
|
||||
minDurStr, _ := c.GetString("freeswitch", "min_dur_low_balance")
|
||||
if cfg.FSMinDurLowBalance, err = utils.ParseDurationWithSecs(minDurStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("freeswitch", "low_balance_ann_file"); hasOpt {
|
||||
cfg.FSLowBalanceAnnFile, _ = c.GetString("freeswitch", "low_balance_ann_file")
|
||||
}
|
||||
if hasOpt = c.HasOption("freeswitch", "empty_balance_context"); hasOpt {
|
||||
cfg.FSEmptyBalanceContext, _ = c.GetString("freeswitch", "empty_balance_context")
|
||||
}
|
||||
if hasOpt = c.HasOption("freeswitch", "empty_balance_ann_file"); hasOpt {
|
||||
cfg.FSEmptyBalanceAnnFile, _ = c.GetString("freeswitch", "empty_balance_ann_file")
|
||||
}
|
||||
if hasOpt = c.HasOption("freeswitch", "cdr_extra_fields"); hasOpt {
|
||||
extraFieldsStr, _ := c.GetString("freeswitch", "cdr_extra_fields")
|
||||
if extraFields, err := utils.ParseRSRFields(extraFieldsStr, utils.FIELDS_SEP); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cfg.FSCdrExtraFields = extraFields
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("opensips", "listen_udp"); hasOpt {
|
||||
cfg.OsipsListenUdp, _ = c.GetString("opensips", "listen_udp")
|
||||
}
|
||||
if hasOpt = c.HasOption("opensips", "mi_addr"); hasOpt {
|
||||
cfg.OsipsMiAddr, _ = c.GetString("opensips", "mi_addr")
|
||||
}
|
||||
if hasOpt = c.HasOption("opensips", "events_subscribe_interval"); hasOpt {
|
||||
evSubscIntervalStr, _ := c.GetString("opensips", "events_subscribe_interval")
|
||||
if cfg.OsipsEvSubscInterval, err = utils.ParseDurationWithSecs(evSubscIntervalStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("opensips", "reconnects"); hasOpt {
|
||||
cfg.OsipsReconnects, _ = c.GetInt("opensips", "reconnects")
|
||||
}
|
||||
if cfg.DerivedChargers, err = ParseCfgDerivedCharging(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasOpt = c.HasOption("derived_charging", "combined_chargers"); hasOpt {
|
||||
cfg.CombinedDerivedChargers, _ = c.GetBool("derived_charging", "combined_chargers")
|
||||
}
|
||||
if hasOpt = c.HasOption("history_agent", "enabled"); hasOpt {
|
||||
cfg.HistoryAgentEnabled, _ = c.GetBool("history_agent", "enabled")
|
||||
}
|
||||
@@ -737,8 +644,8 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) {
|
||||
}
|
||||
if hasOpt = c.HasOption("history_server", "save_interval"); hasOpt {
|
||||
saveIntvlStr, _ := c.GetString("history_server", "save_interval")
|
||||
if cfg.HistorySaveInterval, errParse = utils.ParseDurationWithSecs(saveIntvlStr); errParse != nil {
|
||||
return nil, errParse
|
||||
if cfg.HistorySaveInterval, err = utils.ParseDurationWithSecs(saveIntvlStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hasOpt = c.HasOption("mailer", "server"); hasOpt {
|
||||
|
||||
@@ -32,16 +32,14 @@ func TestLoadXmlCfg(t *testing.T) {
|
||||
return
|
||||
}
|
||||
cfgPath := path.Join(*dataDir, "conf", "samples", "config_local_test.cfg")
|
||||
cfg, err := NewCGRConfig(&cfgPath)
|
||||
cfg, err := NewCGRConfigFromFile(&cfgPath)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if cfg.XmlCfgDocument == nil {
|
||||
t.Error("Did not load the XML Config Document")
|
||||
}
|
||||
if cdreFWCfg, err := cfg.XmlCfgDocument.GetCdreFWCfg("CDREFW-A"); err != nil {
|
||||
t.Error(err)
|
||||
} else if cdreFWCfg == nil {
|
||||
if cdreFWCfg := cfg.XmlCfgDocument.GetCdreCfgs("CDREFW-A"); cdreFWCfg == nil {
|
||||
t.Error("Could not retrieve CDRExporter FixedWidth config instance")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,6 +21,7 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -68,77 +69,75 @@ func TestDefaults(t *testing.T) {
|
||||
eCfg.RPCGOBListen = "127.0.0.1:2013"
|
||||
eCfg.HTTPListen = "127.0.0.1:2080"
|
||||
eCfg.DefaultReqType = utils.RATED
|
||||
eCfg.DefaultTOR = "call"
|
||||
eCfg.DefaultCategory = "call"
|
||||
eCfg.DefaultTenant = "cgrates.org"
|
||||
eCfg.DefaultSubject = "cgrates"
|
||||
eCfg.RoundingMethod = utils.ROUNDING_MIDDLE
|
||||
eCfg.RoundingDecimals = 4
|
||||
eCfg.RoundingDecimals = 10
|
||||
eCfg.HttpSkipTlsVerify = false
|
||||
eCfg.XmlCfgDocument = nil
|
||||
eCfg.RaterEnabled = false
|
||||
eCfg.RaterBalancer = ""
|
||||
eCfg.BalancerEnabled = false
|
||||
eCfg.SchedulerEnabled = false
|
||||
eCfg.CdreDefaultInstance, _ = NewDefaultCdreConfig()
|
||||
eCfg.CDRSEnabled = false
|
||||
eCfg.CDRSExtraFields = []*utils.RSRField{}
|
||||
eCfg.CDRSMediator = ""
|
||||
eCfg.CdreCdrFormat = "csv"
|
||||
eCfg.CdreDir = "/var/log/cgrates/cdr/cdrexport/csv"
|
||||
eCfg.CDRSStats = ""
|
||||
eCfg.CDRSStoreDisable = false
|
||||
eCfg.CDRStatsEnabled = false
|
||||
eCfg.CDRStatConfig = &CdrStatsConfig{Id: utils.META_DEFAULT, QueueLength: 50, TimeWindow: time.Duration(1) * time.Hour, Metrics: []string{"ASR", "ACD", "ACC"}}
|
||||
eCfg.CdrcEnabled = false
|
||||
eCfg.CdrcCdrs = utils.INTERNAL
|
||||
eCfg.CdrcCdrsMethod = "http_cgr"
|
||||
eCfg.CdrcRunDelay = time.Duration(0)
|
||||
eCfg.CdrcCdrType = "csv"
|
||||
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdr/cdrc/in"
|
||||
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdr/cdrc/out"
|
||||
eCfg.CdrcSourceId = "freeswitch_csv"
|
||||
eCfg.CdrcAccIdField = "0"
|
||||
eCfg.CdrcReqTypeField = "1"
|
||||
eCfg.CdrcDirectionField = "2"
|
||||
eCfg.CdrcTenantField = "3"
|
||||
eCfg.CdrcTorField = "4"
|
||||
eCfg.CdrcAccountField = "5"
|
||||
eCfg.CdrcSubjectField = "6"
|
||||
eCfg.CdrcDestinationField = "7"
|
||||
eCfg.CdrcSetupTimeField = "8"
|
||||
eCfg.CdrcAnswerTimeField = "9"
|
||||
eCfg.CdrcDurationField = "10"
|
||||
eCfg.CdrcExtraFields = []string{}
|
||||
eCfg.CdrcCsvSep = string(utils.CSV_SEP)
|
||||
eCfg.CdrcCdrInDir = "/var/log/cgrates/cdrc/in"
|
||||
eCfg.CdrcCdrOutDir = "/var/log/cgrates/cdrc/out"
|
||||
eCfg.CdrcSourceId = "csv"
|
||||
eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
|
||||
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "2"}},
|
||||
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "3"}},
|
||||
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "4"}},
|
||||
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "5"}},
|
||||
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
|
||||
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "7"}},
|
||||
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "8"}},
|
||||
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "9"}},
|
||||
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "10"}},
|
||||
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "11"}},
|
||||
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "12"}},
|
||||
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "13"}},
|
||||
}
|
||||
eCfg.MediatorEnabled = false
|
||||
eCfg.MediatorRater = "internal"
|
||||
eCfg.MediatorRaterReconnects = 3
|
||||
eCfg.MediatorRunIds = []string{}
|
||||
eCfg.MediatorSubjectFields = []string{}
|
||||
eCfg.MediatorReqTypeFields = []string{}
|
||||
eCfg.MediatorDirectionFields = []string{}
|
||||
eCfg.MediatorTenantFields = []string{}
|
||||
eCfg.MediatorTORFields = []string{}
|
||||
eCfg.MediatorAccountFields = []string{}
|
||||
eCfg.MediatorDestFields = []string{}
|
||||
eCfg.MediatorSetupTimeFields = []string{}
|
||||
eCfg.MediatorAnswerTimeFields = []string{}
|
||||
eCfg.MediatorDurationFields = []string{}
|
||||
eCfg.MediatorRater = utils.INTERNAL
|
||||
eCfg.MediatorReconnects = 3
|
||||
eCfg.MediatorStats = utils.INTERNAL
|
||||
eCfg.MediatorStoreDisable = false
|
||||
eCfg.SMEnabled = false
|
||||
eCfg.SMSwitchType = FS
|
||||
eCfg.SMRater = "internal"
|
||||
eCfg.SMRaterReconnects = 3
|
||||
eCfg.SMRater = utils.INTERNAL
|
||||
eCfg.SMCdrS = ""
|
||||
eCfg.SMReconnects = 3
|
||||
eCfg.SMDebitInterval = 10
|
||||
eCfg.SMMinCallDuration = time.Duration(0)
|
||||
eCfg.SMMaxCallDuration = time.Duration(3) * time.Hour
|
||||
eCfg.SMRunIds = []string{}
|
||||
eCfg.SMReqTypeFields = []string{}
|
||||
eCfg.SMDirectionFields = []string{}
|
||||
eCfg.SMTenantFields = []string{}
|
||||
eCfg.SMTORFields = []string{}
|
||||
eCfg.SMAccountFields = []string{}
|
||||
eCfg.SMSubjectFields = []string{}
|
||||
eCfg.SMDestFields = []string{}
|
||||
eCfg.SMSetupTimeFields = []string{}
|
||||
eCfg.SMAnswerTimeFields = []string{}
|
||||
eCfg.SMDurationFields = []string{}
|
||||
eCfg.FreeswitchServer = "127.0.0.1:8021"
|
||||
eCfg.FreeswitchPass = "ClueCon"
|
||||
eCfg.FreeswitchReconnects = 5
|
||||
eCfg.FSMinDurLowBalance = time.Duration(5) * time.Second
|
||||
eCfg.FSLowBalanceAnnFile = ""
|
||||
eCfg.FSEmptyBalanceContext = ""
|
||||
eCfg.FSEmptyBalanceAnnFile = ""
|
||||
eCfg.FSCdrExtraFields = []*utils.RSRField{}
|
||||
eCfg.OsipsListenUdp = "127.0.0.1:2020"
|
||||
eCfg.OsipsMiAddr = "127.0.0.1:8020"
|
||||
eCfg.OsipsEvSubscInterval = time.Duration(60) * time.Second
|
||||
eCfg.OsipsReconnects = 3
|
||||
eCfg.DerivedChargers = make(utils.DerivedChargers, 0)
|
||||
eCfg.CombinedDerivedChargers = true
|
||||
eCfg.HistoryAgentEnabled = false
|
||||
eCfg.HistoryServer = "internal"
|
||||
eCfg.HistoryServer = utils.INTERNAL
|
||||
eCfg.HistoryServerEnabled = false
|
||||
eCfg.HistoryDir = "/var/log/cgrates/history"
|
||||
eCfg.HistorySaveInterval = time.Duration(1) * time.Second
|
||||
@@ -146,28 +145,13 @@ func TestDefaults(t *testing.T) {
|
||||
eCfg.MailerAuthUser = "cgrates"
|
||||
eCfg.MailerAuthPass = "CGRateS.org"
|
||||
eCfg.MailerFromAddr = "cgr-mailer@localhost.localdomain"
|
||||
eCfg.CdreExportedFields = []*utils.RSRField{
|
||||
&utils.RSRField{Id: utils.CGRID},
|
||||
&utils.RSRField{Id: utils.MEDI_RUNID},
|
||||
&utils.RSRField{Id: utils.ACCID},
|
||||
&utils.RSRField{Id: utils.CDRHOST},
|
||||
&utils.RSRField{Id: utils.REQTYPE},
|
||||
&utils.RSRField{Id: utils.DIRECTION},
|
||||
&utils.RSRField{Id: utils.TENANT},
|
||||
&utils.RSRField{Id: utils.TOR},
|
||||
&utils.RSRField{Id: utils.ACCOUNT},
|
||||
&utils.RSRField{Id: utils.SUBJECT},
|
||||
&utils.RSRField{Id: utils.DESTINATION},
|
||||
&utils.RSRField{Id: utils.SETUP_TIME},
|
||||
&utils.RSRField{Id: utils.ANSWER_TIME},
|
||||
&utils.RSRField{Id: utils.DURATION},
|
||||
&utils.RSRField{Id: utils.COST},
|
||||
}
|
||||
eCfg.DataFolderPath = "/usr/share/cgrates/"
|
||||
if !reflect.DeepEqual(cfg, eCfg) {
|
||||
t.Log(eCfg)
|
||||
t.Log(cfg)
|
||||
t.Error("Defaults different than expected!")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSanityCheck(t *testing.T) {
|
||||
@@ -179,21 +163,26 @@ func TestSanityCheck(t *testing.T) {
|
||||
if err := cfg.checkConfigSanity(); err != nil {
|
||||
t.Error("Invalid defaults: ", err)
|
||||
}
|
||||
cfg.SMSubjectFields = []string{"sample1", "sample2", "sample3"}
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect config insanity")
|
||||
}
|
||||
cfg = &CGRConfig{}
|
||||
cfg.CdreCdrFormat = utils.CDRE_FIXED_WIDTH
|
||||
cfg.CdrcEnabled = true
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect fixed_width dependency on xml configuration")
|
||||
t.Error("Failed to detect missing CDR fields definition")
|
||||
}
|
||||
cfg.CdrcCdrType = utils.CSV
|
||||
cfg.CdrcCdrFields = map[string][]*utils.RSRField{utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}}}
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect improper use of CDR field names")
|
||||
}
|
||||
cfg.CdrcCdrFields = map[string][]*utils.RSRField{"extra1": []*utils.RSRField{&utils.RSRField{Id: "test"}}}
|
||||
if err := cfg.checkConfigSanity(); err == nil {
|
||||
t.Error("Failed to detect improper use of CDR field names")
|
||||
}
|
||||
}
|
||||
|
||||
// Load config from file and make sure we have all set
|
||||
func TestConfigFromFile(t *testing.T) {
|
||||
cfgPth := "test_data.txt"
|
||||
cfg, err := NewCGRConfig(&cfgPth)
|
||||
cfg, err := NewCGRConfigFromFile(&cfgPth)
|
||||
if err != nil {
|
||||
t.Log(fmt.Sprintf("Could not parse config: %s!", err))
|
||||
t.FailNow()
|
||||
@@ -223,11 +212,11 @@ func TestConfigFromFile(t *testing.T) {
|
||||
eCfg.RPCGOBListen = "test"
|
||||
eCfg.HTTPListen = "test"
|
||||
eCfg.DefaultReqType = "test"
|
||||
eCfg.DefaultTOR = "test"
|
||||
eCfg.DefaultCategory = "test"
|
||||
eCfg.DefaultTenant = "test"
|
||||
eCfg.DefaultSubject = "test"
|
||||
eCfg.RoundingMethod = "test"
|
||||
eCfg.RoundingDecimals = 99
|
||||
eCfg.HttpSkipTlsVerify = true
|
||||
eCfg.RaterEnabled = true
|
||||
eCfg.RaterBalancer = "test"
|
||||
eCfg.BalancerEnabled = true
|
||||
@@ -235,63 +224,77 @@ func TestConfigFromFile(t *testing.T) {
|
||||
eCfg.CDRSEnabled = true
|
||||
eCfg.CDRSExtraFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
|
||||
eCfg.CDRSMediator = "test"
|
||||
eCfg.CdreCdrFormat = "test"
|
||||
eCfg.CdreExportedFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
|
||||
eCfg.CdreDir = "test"
|
||||
eCfg.CDRSStats = "test"
|
||||
eCfg.CDRSStoreDisable = true
|
||||
eCfg.CDRStatsEnabled = true
|
||||
eCfg.CDRStatConfig = &CdrStatsConfig{Id: utils.META_DEFAULT, QueueLength: 99, TimeWindow: time.Duration(99) * time.Second,
|
||||
Metrics: []string{"test"}, TORs: []string{"test"}, CdrHosts: []string{"test"}, CdrSources: []string{"test"}, ReqTypes: []string{"test"}, Directions: []string{"test"},
|
||||
Tenants: []string{"test"}, Categories: []string{"test"}, Accounts: []string{"test"}, Subjects: []string{"test"}, DestinationPrefixes: []string{"test"},
|
||||
UsageInterval: []time.Duration{time.Duration(99) * time.Second},
|
||||
MediationRunIds: []string{"test"}, RatedAccounts: []string{"test"}, RatedSubjects: []string{"test"}, CostInterval: []float64{99.0}}
|
||||
eCfg.CDRSStats = "test"
|
||||
eCfg.CdreDefaultInstance = &CdreConfig{
|
||||
CdrFormat: "test",
|
||||
FieldSeparator: utils.CSV_SEP,
|
||||
DataUsageMultiplyFactor: 99.0,
|
||||
CostMultiplyFactor: 99.0,
|
||||
CostRoundingDecimals: 99,
|
||||
CostShiftDigits: 99,
|
||||
MaskDestId: "test",
|
||||
MaskLength: 99,
|
||||
ExportDir: "test"}
|
||||
eCfg.CdreDefaultInstance.ContentFields, _ = NewCdreCdrFieldsFromIds(false, "test")
|
||||
eCfg.CdrcEnabled = true
|
||||
eCfg.CdrcCdrs = "test"
|
||||
eCfg.CdrcCdrsMethod = "test"
|
||||
eCfg.CdrcRunDelay = time.Duration(99) * time.Second
|
||||
eCfg.CdrcCdrType = "test"
|
||||
eCfg.CdrcCsvSep = ";"
|
||||
eCfg.CdrcCdrInDir = "test"
|
||||
eCfg.CdrcCdrOutDir = "test"
|
||||
eCfg.CdrcSourceId = "test"
|
||||
eCfg.CdrcAccIdField = "test"
|
||||
eCfg.CdrcReqTypeField = "test"
|
||||
eCfg.CdrcDirectionField = "test"
|
||||
eCfg.CdrcTenantField = "test"
|
||||
eCfg.CdrcTorField = "test"
|
||||
eCfg.CdrcAccountField = "test"
|
||||
eCfg.CdrcSubjectField = "test"
|
||||
eCfg.CdrcDestinationField = "test"
|
||||
eCfg.CdrcSetupTimeField = "test"
|
||||
eCfg.CdrcAnswerTimeField = "test"
|
||||
eCfg.CdrcDurationField = "test"
|
||||
eCfg.CdrcExtraFields = []string{"test"}
|
||||
eCfg.CdrcCdrFields = map[string][]*utils.RSRField{
|
||||
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
"test": []*utils.RSRField{&utils.RSRField{Id: "test"}},
|
||||
}
|
||||
eCfg.MediatorEnabled = true
|
||||
eCfg.MediatorRater = "test"
|
||||
eCfg.MediatorRaterReconnects = 99
|
||||
eCfg.MediatorRunIds = []string{"test"}
|
||||
eCfg.MediatorSubjectFields = []string{"test"}
|
||||
eCfg.MediatorReqTypeFields = []string{"test"}
|
||||
eCfg.MediatorDirectionFields = []string{"test"}
|
||||
eCfg.MediatorTenantFields = []string{"test"}
|
||||
eCfg.MediatorTORFields = []string{"test"}
|
||||
eCfg.MediatorAccountFields = []string{"test"}
|
||||
eCfg.MediatorDestFields = []string{"test"}
|
||||
eCfg.MediatorSetupTimeFields = []string{"test"}
|
||||
eCfg.MediatorAnswerTimeFields = []string{"test"}
|
||||
eCfg.MediatorDurationFields = []string{"test"}
|
||||
eCfg.MediatorReconnects = 99
|
||||
eCfg.MediatorStats = "test"
|
||||
eCfg.MediatorStoreDisable = true
|
||||
eCfg.SMEnabled = true
|
||||
eCfg.SMSwitchType = "test"
|
||||
eCfg.SMRater = "test"
|
||||
eCfg.SMRaterReconnects = 99
|
||||
eCfg.SMCdrS = "test"
|
||||
eCfg.SMReconnects = 99
|
||||
eCfg.SMDebitInterval = 99
|
||||
eCfg.SMMinCallDuration = time.Duration(98) * time.Second
|
||||
eCfg.SMMaxCallDuration = time.Duration(99) * time.Second
|
||||
eCfg.SMRunIds = []string{"test"}
|
||||
eCfg.SMReqTypeFields = []string{"test"}
|
||||
eCfg.SMDirectionFields = []string{"test"}
|
||||
eCfg.SMTenantFields = []string{"test"}
|
||||
eCfg.SMTORFields = []string{"test"}
|
||||
eCfg.SMAccountFields = []string{"test"}
|
||||
eCfg.SMSubjectFields = []string{"test"}
|
||||
eCfg.SMDestFields = []string{"test"}
|
||||
eCfg.SMSetupTimeFields = []string{"test"}
|
||||
eCfg.SMAnswerTimeFields = []string{"test"}
|
||||
eCfg.SMDurationFields = []string{"test"}
|
||||
eCfg.FreeswitchServer = "test"
|
||||
eCfg.FreeswitchPass = "test"
|
||||
eCfg.FreeswitchReconnects = 99
|
||||
eCfg.FSMinDurLowBalance = time.Duration(99) * time.Second
|
||||
eCfg.FSLowBalanceAnnFile = "test"
|
||||
eCfg.FSEmptyBalanceContext = "test"
|
||||
eCfg.FSEmptyBalanceAnnFile = "test"
|
||||
eCfg.FSCdrExtraFields = []*utils.RSRField{&utils.RSRField{Id: "test"}}
|
||||
eCfg.OsipsListenUdp = "test"
|
||||
eCfg.OsipsMiAddr = "test"
|
||||
eCfg.OsipsEvSubscInterval = time.Duration(99) * time.Second
|
||||
eCfg.OsipsReconnects = 99
|
||||
eCfg.DerivedChargers = utils.DerivedChargers{&utils.DerivedCharger{RunId: "test", RunFilters: "", ReqTypeField: "test", DirectionField: "test", TenantField: "test",
|
||||
CategoryField: "test", AccountField: "test", SubjectField: "test", DestinationField: "test", SetupTimeField: "test", AnswerTimeField: "test", UsageField: "test"}}
|
||||
eCfg.CombinedDerivedChargers = true
|
||||
eCfg.HistoryAgentEnabled = true
|
||||
eCfg.HistoryServer = "test"
|
||||
eCfg.HistoryServerEnabled = true
|
||||
@@ -301,9 +304,91 @@ func TestConfigFromFile(t *testing.T) {
|
||||
eCfg.MailerAuthUser = "test"
|
||||
eCfg.MailerAuthPass = "test"
|
||||
eCfg.MailerFromAddr = "test"
|
||||
eCfg.DataFolderPath = "/usr/share/cgrates/"
|
||||
if !reflect.DeepEqual(cfg, eCfg) {
|
||||
t.Log(eCfg)
|
||||
t.Log(cfg)
|
||||
t.Error("Loading of configuration from file failed!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdrsExtraFields(t *testing.T) {
|
||||
eFieldsCfg := []byte(`[cdrs]
|
||||
extra_fields = extr1,extr2
|
||||
`)
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CDRSExtraFields, []*utils.RSRField{&utils.RSRField{Id: "extr1"}, &utils.RSRField{Id: "extr2"}}) {
|
||||
t.Errorf("Unexpected value for CdrsExtraFields: %v", cfg.CDRSExtraFields)
|
||||
}
|
||||
eFieldsCfg = []byte(`[cdrs]
|
||||
extra_fields = ~effective_caller_id_number:s/(\d+)/+$1/
|
||||
`)
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CDRSExtraFields, []*utils.RSRField{&utils.RSRField{Id: "effective_caller_id_number",
|
||||
RSRules: []*utils.ReSearchReplace{&utils.ReSearchReplace{SearchRegexp: regexp.MustCompile(`(\d+)`), ReplaceTemplate: "+$1"}}}}) {
|
||||
t.Errorf("Unexpected value for config CdrsExtraFields: %v", cfg.CDRSExtraFields)
|
||||
}
|
||||
eFieldsCfg = []byte(`[cdrs]
|
||||
extra_fields = extr1,~extr2:s/x.+/
|
||||
`)
|
||||
if _, err := NewCGRConfigFromBytes(eFieldsCfg); err == nil {
|
||||
t.Error("Failed to detect failed RSRParsing")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCdreExtraFields(t *testing.T) {
|
||||
eFieldsCfg := []byte(`[cdre]
|
||||
cdr_format = csv
|
||||
export_template = cgrid,mediation_runid,accid
|
||||
`)
|
||||
expectedFlds := []*CdreCdrField{
|
||||
&CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Mandatory: true},
|
||||
&CdreCdrField{Name: "mediation_runid", Type: utils.CDRFIELD, Value: "mediation_runid", valueAsRsrField: &utils.RSRField{Id: "mediation_runid"},
|
||||
Mandatory: true},
|
||||
&CdreCdrField{Name: "accid", Type: utils.CDRFIELD, Value: "accid", valueAsRsrField: &utils.RSRField{Id: "accid"},
|
||||
Mandatory: true},
|
||||
}
|
||||
expCdreCfg := &CdreConfig{CdrFormat: utils.CSV, FieldSeparator: utils.CSV_SEP, CostRoundingDecimals: -1, ExportDir: "/var/log/cgrates/cdre", ContentFields: expectedFlds}
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CdreDefaultInstance, expCdreCfg) {
|
||||
t.Errorf("Expecting: %v, received: %v", expCdreCfg, cfg.CdreDefaultInstance)
|
||||
}
|
||||
eFieldsCfg = []byte(`[cdre]
|
||||
cdr_format = csv
|
||||
export_template = cgrid,~effective_caller_id_number:s/(\d+)/+$1/
|
||||
`)
|
||||
rsrField, _ := utils.NewRSRField(`~effective_caller_id_number:s/(\d+)/+$1/`)
|
||||
expectedFlds = []*CdreCdrField{
|
||||
&CdreCdrField{Name: "cgrid", Type: utils.CDRFIELD, Value: "cgrid", valueAsRsrField: &utils.RSRField{Id: "cgrid"}, Mandatory: true},
|
||||
&CdreCdrField{Name: `~effective_caller_id_number:s/(\d+)/+$1/`, Type: utils.CDRFIELD, Value: `~effective_caller_id_number:s/(\d+)/+$1/`,
|
||||
valueAsRsrField: rsrField, Mandatory: false}}
|
||||
expCdreCfg.ContentFields = expectedFlds
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CdreDefaultInstance, expCdreCfg) {
|
||||
t.Errorf("Expecting: %v, received: %v", expCdreCfg, cfg.CdreDefaultInstance)
|
||||
}
|
||||
eFieldsCfg = []byte(`[cdre]
|
||||
cdr_format = csv
|
||||
export_template = cgrid,~accid:s/(\d)/$1,runid
|
||||
`)
|
||||
if _, err := NewCGRConfigFromBytes(eFieldsCfg); err == nil {
|
||||
t.Error("Failed to detect failed RSRParsing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdrcCdrDefaultFields(t *testing.T) {
|
||||
cdrcCfg := []byte(`[cdrc]
|
||||
enabled = true
|
||||
`)
|
||||
cfgDefault, _ := NewDefaultCGRConfig()
|
||||
if cfg, err := NewCGRConfigFromBytes(cdrcCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, cfgDefault.CdrcCdrFields) {
|
||||
t.Errorf("Unexpected value for CdrcCdrFields: %v", cfg.CdrcCdrFields)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,46 +21,135 @@ package config
|
||||
import (
|
||||
"code.google.com/p/goconf/conf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// Adds support for slice values in config
|
||||
func ConfigSlice(c *conf.ConfigFile, section, valName string) ([]string, error) {
|
||||
sliceStr, errGet := c.GetString(section, valName)
|
||||
if errGet != nil {
|
||||
return nil, errGet
|
||||
}
|
||||
cfgValStrs := strings.Split(sliceStr, ",") // If need arrises, we can make the separator configurable
|
||||
if len(cfgValStrs) == 1 && cfgValStrs[0] == "" { // Prevents returning iterable with empty value
|
||||
return []string{}, nil
|
||||
}
|
||||
for _, elm := range cfgValStrs {
|
||||
if elm == "" { //One empty element is presented when splitting empty string
|
||||
return nil, errors.New("Empty values in config slice")
|
||||
|
||||
}
|
||||
func ConfigSlice(cfgVal string) ([]string, error) {
|
||||
cfgValStrs := strings.Split(cfgVal, utils.FIELDS_SEP) // If need arrises, we can make the separator configurable
|
||||
for idx, elm := range cfgValStrs {
|
||||
cfgValStrs[idx] = strings.TrimSpace(elm) // By default spaces are not removed so we do it here to avoid unpredicted results in config
|
||||
}
|
||||
return cfgValStrs, nil
|
||||
}
|
||||
|
||||
func ParseRSRFields(configVal string) ([]*utils.RSRField, error) {
|
||||
cfgValStrs := strings.Split(configVal, string(utils.CSV_SEP))
|
||||
if len(cfgValStrs) == 1 && cfgValStrs[0] == "" { // Prevents returning iterable with empty value
|
||||
return []*utils.RSRField{}, nil
|
||||
// Parse the configuration file and returns utils.DerivedChargers instance if no errors
|
||||
func ParseCfgDerivedCharging(c *conf.ConfigFile) (dcs utils.DerivedChargers, err error) {
|
||||
var runIds, runFilters, reqTypeFlds, directionFlds, tenantFlds, torFlds, acntFlds, subjFlds, dstFlds, sTimeFlds, aTimeFlds, durFlds []string
|
||||
cfgVal, _ := c.GetString("derived_charging", "run_ids")
|
||||
if runIds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsrFields := make([]*utils.RSRField, len(cfgValStrs))
|
||||
for idx, cfgValStr := range cfgValStrs {
|
||||
if len(cfgValStr) == 0 { //One empty element is presented when splitting empty string
|
||||
return nil, errors.New("Empty values in config slice")
|
||||
|
||||
cfgVal, _ = c.GetString("derived_charging", "run_filters")
|
||||
if runFilters, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "reqtype_fields")
|
||||
if reqTypeFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "direction_fields")
|
||||
if directionFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "tenant_fields")
|
||||
if tenantFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "category_fields")
|
||||
if torFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "account_fields")
|
||||
if acntFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "subject_fields")
|
||||
if subjFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "destination_fields")
|
||||
if dstFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "setup_time_fields")
|
||||
if sTimeFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "answer_time_fields")
|
||||
if aTimeFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgVal, _ = c.GetString("derived_charging", "usage_fields")
|
||||
if durFlds, err = ConfigSlice(cfgVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We need all to be the same length
|
||||
if len(runFilters) != len(runIds) ||
|
||||
len(reqTypeFlds) != len(runIds) ||
|
||||
len(directionFlds) != len(runIds) ||
|
||||
len(tenantFlds) != len(runIds) ||
|
||||
len(torFlds) != len(runIds) ||
|
||||
len(acntFlds) != len(runIds) ||
|
||||
len(subjFlds) != len(runIds) ||
|
||||
len(dstFlds) != len(runIds) ||
|
||||
len(sTimeFlds) != len(runIds) ||
|
||||
len(aTimeFlds) != len(runIds) ||
|
||||
len(durFlds) != len(runIds) {
|
||||
return nil, errors.New("<ConfigSanity> Inconsistent fields length in derivated_charging section")
|
||||
}
|
||||
// Create the individual chargers and append them to the final instance
|
||||
dcs = make(utils.DerivedChargers, 0)
|
||||
if len(runIds) == 1 && len(runIds[0]) == 0 { // Avoid iterating on empty runid
|
||||
return dcs, nil
|
||||
}
|
||||
for runIdx, runId := range runIds {
|
||||
dc, err := utils.NewDerivedCharger(runId, runFilters[runIdx], reqTypeFlds[runIdx], directionFlds[runIdx], tenantFlds[runIdx], torFlds[runIdx],
|
||||
acntFlds[runIdx], subjFlds[runIdx], dstFlds[runIdx], sTimeFlds[runIdx], aTimeFlds[runIdx], durFlds[runIdx])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rsrField, err := utils.NewRSRField(cfgValStr); err != nil {
|
||||
if dcs, err = dcs.Append(dc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dcs, nil
|
||||
}
|
||||
|
||||
func ParseCdrcCdrFields(torFld, accIdFld, reqtypeFld, directionFld, tenantFld, categoryFld, acntFld, subjectFld, destFld,
|
||||
setupTimeFld, answerTimeFld, durFld, extraFlds string) (map[string][]*utils.RSRField, error) {
|
||||
cdrcCdrFlds := make(map[string][]*utils.RSRField)
|
||||
if len(extraFlds) != 0 {
|
||||
if sepExtraFlds, err := ConfigSlice(extraFlds); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
rsrFields[idx] = rsrField
|
||||
for _, fldStr := range sepExtraFlds {
|
||||
// extra fields defined as: <label_extrafield_1>:<index_extrafield_1>
|
||||
if spltLbl := strings.Split(fldStr, utils.CONCATENATED_KEY_SEP); len(spltLbl) != 2 {
|
||||
return nil, fmt.Errorf("Wrong format for cdrc.extra_fields: %s", fldStr)
|
||||
} else {
|
||||
if rsrFlds, err := utils.ParseRSRFields(spltLbl[1], utils.INFIELD_SEP); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cdrcCdrFlds[spltLbl[0]] = rsrFlds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rsrFields, nil
|
||||
for fldTag, fldVal := range map[string]string{utils.TOR: torFld, utils.ACCID: accIdFld, utils.REQTYPE: reqtypeFld, utils.DIRECTION: directionFld, utils.TENANT: tenantFld,
|
||||
utils.CATEGORY: categoryFld, utils.ACCOUNT: acntFld, utils.SUBJECT: subjectFld, utils.DESTINATION: destFld, utils.SETUP_TIME: setupTimeFld,
|
||||
utils.ANSWER_TIME: answerTimeFld, utils.USAGE: durFld} {
|
||||
if len(fldVal) != 0 {
|
||||
if rsrFlds, err := utils.ParseRSRFields(fldVal, utils.INFIELD_SEP); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cdrcCdrFlds[fldTag] = rsrFlds
|
||||
}
|
||||
}
|
||||
}
|
||||
return cdrcCdrFlds, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,21 +19,122 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func TestParseRSRFields(t *testing.T) {
|
||||
fields := `host,~sip_redirected_to:s/sip:\+49(\d+)@/0$1/,destination`
|
||||
expectParsedFields := []*utils.RSRField{&utils.RSRField{Id: "host"},
|
||||
&utils.RSRField{Id: "sip_redirected_to", RSRule: &utils.ReSearchReplace{regexp.MustCompile(`sip:\+49(\d+)@`), "0$1"}},
|
||||
&utils.RSRField{Id: "destination"}}
|
||||
if parsedFields, err := ParseRSRFields(fields); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if !reflect.DeepEqual(parsedFields, expectParsedFields) {
|
||||
t.Errorf("Unexpected value of parsed fields")
|
||||
func TestConfigSlice(t *testing.T) {
|
||||
eCS := []string{"", ""}
|
||||
if cs, err := ConfigSlice(" , "); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else if !reflect.DeepEqual(eCS, cs) {
|
||||
t.Errorf("Expecting: %v, received: %v", eCS, cs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCfgDerivedCharging(t *testing.T) {
|
||||
eFieldsCfg := []byte(`[derived_charging]
|
||||
run_ids = run1, run2
|
||||
run_filters =,
|
||||
reqtype_fields = test1, test2
|
||||
direction_fields = test1, test2
|
||||
tenant_fields = test1, test2
|
||||
category_fields = test1, test2
|
||||
account_fields = test1, test2
|
||||
subject_fields = test1, test2
|
||||
destination_fields = test1, test2
|
||||
setup_time_fields = test1, test2
|
||||
answer_time_fields = test1, test2
|
||||
usage_fields = test1, test2
|
||||
`)
|
||||
edcs := utils.DerivedChargers{
|
||||
&utils.DerivedCharger{RunId: "run1", ReqTypeField: "test1", DirectionField: "test1", TenantField: "test1", CategoryField: "test1",
|
||||
AccountField: "test1", SubjectField: "test1", DestinationField: "test1", SetupTimeField: "test1", AnswerTimeField: "test1", UsageField: "test1"},
|
||||
&utils.DerivedCharger{RunId: "run2", ReqTypeField: "test2", DirectionField: "test2", TenantField: "test2", CategoryField: "test2",
|
||||
AccountField: "test2", SubjectField: "test2", DestinationField: "test2", SetupTimeField: "test2", AnswerTimeField: "test2", UsageField: "test2"}}
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.DerivedChargers, edcs) {
|
||||
t.Errorf("Expecting: %v, received: %v", edcs, cfg.DerivedChargers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCfgDerivedChargingDn1(t *testing.T) {
|
||||
eFieldsCfg := []byte(`[derived_charging]
|
||||
run_ids = run1, run2
|
||||
run_filters =~account:s/^\w+[mpls]\d{6}$//,~account:s/^0\d{9}$//;^account/value/
|
||||
reqtype_fields = test1, test2
|
||||
direction_fields = test1, test2
|
||||
tenant_fields = test1, test2
|
||||
category_fields = test1, test2
|
||||
account_fields = test1, test2
|
||||
subject_fields = test1, test2
|
||||
destination_fields = test1, test2
|
||||
setup_time_fields = test1, test2
|
||||
answer_time_fields = test1, test2
|
||||
usage_fields = test1, test2
|
||||
`)
|
||||
eDcs := make(utils.DerivedChargers, 2)
|
||||
if dc, err := utils.NewDerivedCharger("run1", `~account:s/^\w+[mpls]\d{6}$//`, "test1", "test1", "test1",
|
||||
"test1", "test1", "test1", "test1", "test1", "test1", "test1"); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else {
|
||||
eDcs[0] = dc
|
||||
}
|
||||
if dc, err := utils.NewDerivedCharger("run2", `~account:s/^0\d{9}$//;^account/value/`, "test2", "test2", "test2",
|
||||
"test2", "test2", "test2", "test2", "test2", "test2", "test2"); err != nil {
|
||||
t.Error("Unexpected error: ", err)
|
||||
} else {
|
||||
eDcs[1] = dc
|
||||
}
|
||||
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.DerivedChargers, eDcs) {
|
||||
dcsJson, _ := json.Marshal(cfg.DerivedChargers)
|
||||
t.Errorf("Received: %s", string(dcsJson))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCdrcCdrFields(t *testing.T) {
|
||||
eFieldsCfg := []byte(`[cdrc]
|
||||
cdr_type = test
|
||||
tor_field = tor1
|
||||
accid_field = accid1
|
||||
reqtype_field = reqtype1
|
||||
direction_field = direction1
|
||||
tenant_field = tenant1
|
||||
category_field = category1
|
||||
account_field = account1
|
||||
subject_field = subject1
|
||||
destination_field = destination1
|
||||
setup_time_field = setuptime1
|
||||
answer_time_field = answertime1
|
||||
usage_field = duration1
|
||||
extra_fields = extra1:extraval1,extra2:extraval1
|
||||
`)
|
||||
eCdrcCdrFlds := map[string][]*utils.RSRField{
|
||||
utils.TOR: []*utils.RSRField{&utils.RSRField{Id: "tor1"}},
|
||||
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "accid1"}},
|
||||
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "reqtype1"}},
|
||||
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "direction1"}},
|
||||
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "tenant1"}},
|
||||
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "category1"}},
|
||||
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "account1"}},
|
||||
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "subject1"}},
|
||||
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "destination1"}},
|
||||
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "setuptime1"}},
|
||||
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "answertime1"}},
|
||||
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "duration1"}},
|
||||
"extra1": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
|
||||
"extra2": []*utils.RSRField{&utils.RSRField{Id: "extraval1"}},
|
||||
}
|
||||
if cfg, err := NewCGRConfigFromBytes(eFieldsCfg); err != nil {
|
||||
t.Error("Could not parse the config", err.Error())
|
||||
} else if !reflect.DeepEqual(cfg.CdrcCdrFields, eCdrcCdrFlds) {
|
||||
t.Errorf("Expecting: %v, received: %v, tor: %v", eCdrcCdrFlds, cfg.CdrcCdrFields, cfg.CdrcCdrFields[utils.TOR])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,23 +14,22 @@ accountdb_port = test # Accounting subsystem port to reach the database.
|
||||
accountdb_name = test # Accounting subsystem database name to connect to.
|
||||
accountdb_user = test # Accounting subsystem username to use when connecting to database.
|
||||
accountdb_passwd = test # Accounting subsystem password to use when connecting to database.
|
||||
stordb_type = test # Log/stored database type to use: <same|postgres|mongo|redis>
|
||||
stordb_type = test # Log/scategoryed database type to use: <same|postgres|mongo|redis>
|
||||
stordb_host = test # The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
stordb_port = test # The port to reach the logdb.
|
||||
stordb_name = test # The name of the log database to connect to.
|
||||
stordb_user = test # Username to use when connecting to logdb.
|
||||
stordb_passwd = test # Password to use when connecting to logdb.
|
||||
dbdata_encoding = test # The encoding used to store object data in strings: <msgpack|json>
|
||||
dbdata_encoding = test # The encoding used to scategorye object data in strings: <msgpack|json>
|
||||
rpc_json_listen = test # RPC JSON listening address
|
||||
rpc_gob_listen = test # RPC GOB listening address
|
||||
http_listen = test # HTTP listening address
|
||||
default_reqtype = test # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
|
||||
default_tor = test # Default Type of Record to consider when missing from requests.
|
||||
default_category = test # Default Type of Record to consider when missing from requests.
|
||||
default_tenant = test # Default Tenant to consider when missing from requests.
|
||||
default_subject = test # Default rating Subject to consider when missing from requests.
|
||||
rounding_method = test # Rounding method for floats/costs: <up|middle|down>
|
||||
rounding_decimals = 99 # Number of decimals to round floats/costs at
|
||||
|
||||
http_skip_tls_veify = true # If enabled Http Client will accept any TLS certificate
|
||||
|
||||
[balancer]
|
||||
enabled = true # Start Balancer service: <true|false>.
|
||||
@@ -44,84 +43,124 @@ enabled = true # Starts Scheduler service: <true|false>.
|
||||
|
||||
[cdrs]
|
||||
enabled = true # Start the CDR Server service: <true|false>.
|
||||
extra_fields = test # Extra fields to store in CDRs
|
||||
extra_fields = test # Extra fields to scategorye in CDRs
|
||||
mediator = test # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
cdrstats = test # Address where to reach the CDRStats server. Empty for disabling stats. <""|internal>
|
||||
store_disable = true # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
|
||||
|
||||
[cdre]
|
||||
cdr_format = test # Exported CDRs format <csv>
|
||||
export_dir = test # Path where the exported CDRs will be placed
|
||||
export_template = test # List of fields in the exported CDRs
|
||||
cdr_format = test # Exported CDRs format <csv>
|
||||
data_usage_multiply_factor = 99.0 # Multiply data usage before export (eg: convert from KBytes to Bytes)
|
||||
cost_multiply_factor = 99.0 # Multiply cost before export (0.0 to disable), eg: add VAT
|
||||
cost_rounding_decimals = 99 # Rounding decimals for Cost values. -1 to disable rounding
|
||||
cost_shift_digits = 99 # Shift digits in the cost on export (eg: convert from EUR to cents)
|
||||
mask_destination_id = test # Destination id containing called addresses to be masked on export
|
||||
mask_length = 99 # Length of the destination suffix to be masked
|
||||
export_dir = test # Path where the exported CDRs will be placed
|
||||
export_template = test # List of fields in the exported CDRs
|
||||
|
||||
[cdrc]
|
||||
enabled = true # Enable CDR client functionality
|
||||
cdrs = test # Address where to reach CDR server
|
||||
cdrs_method = test # Mechanism to use when posting CDRs on server <http_cgr>
|
||||
cdrs = test # Address where to reach CDR server
|
||||
run_delay = 99 # Period to sleep between two runs, 0 to use automation via inotify
|
||||
cdr_type = test # CDR file format <csv>.
|
||||
cdr_in_dir = test # Absolute path towards the directory where the CDRs are kept (file stored CDRs).
|
||||
cdr_out_dir = test # Absolute path towards the directory where processed CDRs will be moved after processing.
|
||||
cdr_source_id = test # Tag identifying the source of the CDRs within CGRS database.
|
||||
csv_separator =; # Csv separator, one character only and should be next to equal sign
|
||||
cdr_in_dir = test # Absolute path towards the direccategoryy where the CDRs are kept (file scategoryed CDRs).
|
||||
cdr_out_dir = test # Absolute path towards the direccategoryy where processed CDRs will be moved after processing.
|
||||
cdr_source_id = test # Tag identifying the source of the CDRs within CGRS database.
|
||||
tor_field = test # TypeOfRecord field identifier. Use index number in case of .csv cdrs.
|
||||
accid_field = test # Accounting id field identifier. Use index number in case of .csv cdrs.
|
||||
reqtype_field = test # Request type field identifier. Use index number in case of .csv cdrs.
|
||||
direction_field = test # Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
reqtype_field = test # Request type field identifier. Use index number in case of .csv cdrs.
|
||||
direction_field = test # Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
tenant_field = test # Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
tor_field = test # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
account_field = test # Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
subject_field = test # Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
destination_field = test # Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
setup_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
answer_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
duration_field = test # Duration field identifier. Use index numbers in case of .csv cdrs.
|
||||
extra_fields = test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1:field1,index2:field2"
|
||||
category_field = test # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
account_field = test # Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
subject_field = test # Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
destination_field = test # Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
setup_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
answer_time_field = test # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
usage_field = test # Duration field identifier. Use index numbers in case of .csv cdrs.
|
||||
extra_fields = test:test # Field identifiers of the fields to add in extra fields section, special format in case of .csv "index1|field1,index2|field2"
|
||||
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
enabled = true # Starts Mediacategory service: <true|false>.
|
||||
rater = test # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
rater_reconnects = 99 # Number of reconnects to rater before giving up.
|
||||
run_ids = test # Identifiers for each mediation run on CDRs
|
||||
subject_fields = test # Name of subject fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
reqtype_fields = test # Name of request type fields to be used during mediation. Use index number in case of .csv cdrs.
|
||||
direction_fields = test # Name of direction fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
tenant_fields = test # Name of tenant fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
tor_fields = test # Name of tor fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
account_fields = test # Name of account fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
destination_fields = test # Name of destination fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
setup_time_fields = test # Name of setup_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
answer_time_fields = test # Name of answer_time fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
duration_fields = test # Name of duration fields to be used during mediation. Use index numbers in case of .csv cdrs.
|
||||
reconnects = 99 # Number of reconnects to rater before giving up.
|
||||
cdrstats = test # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
|
||||
store_disable = true # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
|
||||
|
||||
[cdrstats]
|
||||
enabled = true # Start the CDR stats service: <true|false>.
|
||||
queue_length = 99 # Number of items in the stats buffer
|
||||
time_window = 99 # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
|
||||
metrics = test # Stat metric ids to build
|
||||
setup_interval = # Filter on CDR SetupTime
|
||||
tors = test # Filter on CDR TOR fields
|
||||
cdr_hosts= test # Filter on CDR CdrHost fields
|
||||
cdr_sources = test # Filter on CDR CdrSource fields
|
||||
req_types = test # Filter on CDR ReqType fields
|
||||
directions = test # Filter on CDR Direction fields
|
||||
tenants = test # Filter on CDR Tenant fields
|
||||
categories = test # Filter on CDR Category fields
|
||||
accounts = test # Filter on CDR Account fields
|
||||
subjects = test # Filter on CDR Subject fields
|
||||
destination_prefixes = test # Filter on CDR Destination prefixes
|
||||
usage_interval = 99 # Filter on CDR Usage
|
||||
mediation_run_ids = test # Filter on CDR MediationRunId fields
|
||||
rated_accounts = test # Filter on CDR RatedAccount fields
|
||||
rated_subjects = test # Filter on CDR RatedSubject fields
|
||||
cost_intervals = 99 # Filter on CDR Cost
|
||||
|
||||
[session_manager]
|
||||
enabled = true # Starts SessionManager service: <true|false>.
|
||||
switch_type = test # Defines the type of switch behind: <freeswitch>.
|
||||
rater = test # Address where to reach the Rater.
|
||||
rater_reconnects = 99 # Number of reconnects to rater before giving up.
|
||||
cdrs = test # Address where to reach CDR Server, empty to disable CDR capturing <""|internal|127.0.0.1:2013>
|
||||
reconnects = 99 # Number of reconnects to rater before giving up.
|
||||
debit_interval = 99 # Interval to perform debits on.
|
||||
max_call_duration = 99 # Maximum call duration a prepaid call can last
|
||||
run_ids = test # Identifiers of additional sessions control.
|
||||
reqtype_fields = test # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
direction_fields = test # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tenant_fields = test # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tor_fields = test # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
account_fields = test # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
subject_fields = test # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
destination_fields = test # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
setup_time_fields = test # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
answer_time_fields = test # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
duration_fields = test # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
|
||||
min_call_duration = 98 # Only authorize calls with allowed duration bigger than this
|
||||
max_call_duration = 99 # Maximum call duration a prepaid call can last
|
||||
|
||||
[freeswitch]
|
||||
server = test # Adress where to connect to FreeSWITCH socket.
|
||||
passwd = test # FreeSWITCH socket password.
|
||||
reconnects = 99 # Number of attempts on connect failure.
|
||||
server = test # Adress where to connect to FreeSWITCH socket.
|
||||
passwd = test # FreeSWITCH socket password.
|
||||
reconnects = 99 # Number of attempts on connect failure.
|
||||
min_dur_low_balance = 99 # Threshold which will trigger low balance warnings
|
||||
low_balance_ann_file = test # File to be played when low balance is reached
|
||||
empty_balance_context = test # If defined, call will be transfered to this context on empty balance
|
||||
empty_balance_ann_file = test # File to be played before disconnecting prepaid calls (applies only if no context defined)
|
||||
cdr_extra_fields = test # Extra fields to store in CDRs in case of processing them
|
||||
|
||||
[opensips]
|
||||
listen_udp = test # Address where to listen for event datagrams coming from OpenSIPS
|
||||
mi_addr = test # Adress where to reach OpenSIPS mi_datagram module
|
||||
events_subscribe_interval = 99 # Automatic events subscription to OpenSIPS, 0 to disable it
|
||||
cdrs = test # Address where to reach CDR Server, empty to disable CDR processing <""|internal|127.0.0.1:2013>
|
||||
reconnects = 99 # Number of attempts on connect failure.
|
||||
|
||||
[derived_charging]
|
||||
run_ids = test # Identifiers of additional sessions control.
|
||||
run_filters = # No filters applied
|
||||
reqtype_fields = test # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
direction_fields = test # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tenant_fields = test # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
category_fields = test # Name of category fields to be used during additional sessions control <""|*default|field_name>.
|
||||
account_fields = test # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
subject_fields = test # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
destination_fields = test # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
setup_time_fields = test # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
answer_time_fields = test # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
usage_fields = test # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
|
||||
combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
|
||||
|
||||
[history_server]
|
||||
enabled = true # Starts History service: <true|false>.
|
||||
history_dir = test # Location on disk where to store history files.
|
||||
enabled = true # Starts Hiscategoryy service: <true|false>.
|
||||
history_dir = test # Location on disk where to scategorye hiscategoryy files.
|
||||
save_interval = 99 # Timeout duration between saves
|
||||
|
||||
[history_agent]
|
||||
enabled = true # Starts History as a client: <true|false>.
|
||||
server = test # Address where to reach the master history server: <internal|x.y.z.y:1234>
|
||||
enabled = true # Starts Hiscategoryy as a client: <true|false>.
|
||||
server = test # Address where to reach the master hiscategoryy server: <internal|x.y.z.y:1234>
|
||||
|
||||
[mailer]
|
||||
server = test # The server to use when sending emails out
|
||||
|
||||
87
config/xmlcdrc.go
Normal file
87
config/xmlcdrc.go
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
type CgrXmlCdrcCfg struct {
|
||||
Enabled bool `xml:"enabled"` // Enable/Disable the
|
||||
CdrsAddress string `xml:"cdrs_address"` // The address where CDRs can be reached
|
||||
CdrType string `xml:"cdr_type"` // The type of CDR to process <csv>
|
||||
CsvSeparator string `xml:"field_separator"` // The separator to use when reading csvs
|
||||
RunDelay int64 `xml:"run_delay"` // Delay between runs
|
||||
CdrInDir string `xml:"cdr_in_dir"` // Folder to process CDRs from
|
||||
CdrOutDir string `xml:"cdr_out_dir"` // Folder to move processed CDRs to
|
||||
CdrSourceId string `xml:"cdr_source_id"` // Source identifier for the processed CDRs
|
||||
CdrFields []*CdrcField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// Set the defaults
|
||||
func (cdrcCfg *CgrXmlCdrcCfg) setDefaults() error {
|
||||
dfCfg, _ := NewDefaultCGRConfig()
|
||||
if len(cdrcCfg.CdrsAddress) == 0 {
|
||||
cdrcCfg.CdrsAddress = dfCfg.CdrcCdrs
|
||||
}
|
||||
if len(cdrcCfg.CdrType) == 0 {
|
||||
cdrcCfg.CdrType = dfCfg.CdrcCdrType
|
||||
}
|
||||
if len(cdrcCfg.CsvSeparator) == 0 {
|
||||
cdrcCfg.CsvSeparator = dfCfg.CdrcCsvSep
|
||||
}
|
||||
if len(cdrcCfg.CdrInDir) == 0 {
|
||||
cdrcCfg.CdrInDir = dfCfg.CdrcCdrInDir
|
||||
}
|
||||
if len(cdrcCfg.CdrOutDir) == 0 {
|
||||
cdrcCfg.CdrOutDir = dfCfg.CdrcCdrOutDir
|
||||
}
|
||||
if len(cdrcCfg.CdrSourceId) == 0 {
|
||||
cdrcCfg.CdrSourceId = dfCfg.CdrcSourceId
|
||||
}
|
||||
if len(cdrcCfg.CdrFields) == 0 {
|
||||
for key, cfgRsrFields := range dfCfg.CdrcCdrFields {
|
||||
cdrcCfg.CdrFields = append(cdrcCfg.CdrFields, &CdrcField{Id: key, Value: "PLACEHOLDER", rsrFields: cfgRsrFields})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cdrcCfg *CgrXmlCdrcCfg) CdrRSRFields() map[string][]*utils.RSRField {
|
||||
rsrFields := make(map[string][]*utils.RSRField)
|
||||
for _, fld := range cdrcCfg.CdrFields {
|
||||
rsrFields[fld.Id] = fld.rsrFields
|
||||
}
|
||||
return rsrFields
|
||||
}
|
||||
|
||||
type CdrcField struct {
|
||||
XMLName xml.Name `xml:"field"`
|
||||
Id string `xml:"id,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
rsrFields []*utils.RSRField
|
||||
}
|
||||
|
||||
func (cdrcFld *CdrcField) PopulateRSRFields() (err error) {
|
||||
if cdrcFld.rsrFields, err = utils.ParseRSRFields(cdrcFld.Value, utils.INFIELD_SEP); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
171
config/xmlcdrc_test.go
Normal file
171
config/xmlcdrc_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cfgDocCdrc *CgrXmlCfgDocument // Will be populated by first test
|
||||
|
||||
func TestPopulateRSRFIeld(t *testing.T) {
|
||||
cdrcField := CdrcField{Id: "TEST1", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
|
||||
if err := cdrcField.PopulateRSRFields(); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if cdrcField.rsrFields == nil {
|
||||
t.Error("Failed loading the RSRField")
|
||||
}
|
||||
cdrcField = CdrcField{Id: "TEST2", Value: `99`}
|
||||
if err := cdrcField.PopulateRSRFields(); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if cdrcField.rsrFields == nil {
|
||||
t.Error("Failed loading the RSRField")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaults(t *testing.T) {
|
||||
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdrc" id="CDRC-CSVDF">
|
||||
<enabled>true</enabled>
|
||||
</configuration>
|
||||
</document>`
|
||||
var xmlCdrc *CgrXmlCdrcCfg
|
||||
reader := strings.NewReader(cfgXmlStr)
|
||||
if cfgDocCdrcDf, err := ParseCgrXmlConfig(reader); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfgDocCdrcDf == nil {
|
||||
t.Fatal("Could not parse xml configuration document")
|
||||
} else if len(cfgDocCdrcDf.cdrcs) != 1 {
|
||||
t.Error("Did not load cdrc")
|
||||
} else {
|
||||
xmlCdrc = cfgDocCdrcDf.cdrcs["CDRC-CSVDF"]
|
||||
}
|
||||
dfCfg, _ := NewDefaultCGRConfig()
|
||||
xmlCdrc.setDefaults()
|
||||
if xmlCdrc.CdrsAddress != dfCfg.CdrcCdrs ||
|
||||
xmlCdrc.CdrType != dfCfg.CdrcCdrType ||
|
||||
xmlCdrc.CsvSeparator != dfCfg.CdrcCsvSep ||
|
||||
xmlCdrc.CdrInDir != dfCfg.CdrcCdrInDir ||
|
||||
xmlCdrc.CdrOutDir != dfCfg.CdrcCdrOutDir ||
|
||||
xmlCdrc.CdrSourceId != dfCfg.CdrcSourceId ||
|
||||
len(xmlCdrc.CdrFields) != len(dfCfg.CdrcCdrFields) {
|
||||
t.Error("Failed loading default configuration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseXmlCdrcConfig(t *testing.T) {
|
||||
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdrc" type="csv" id="CDRC-CSV1">
|
||||
<enabled>true</enabled>
|
||||
<cdrs_address>internal</cdrs_address>
|
||||
<cdr_type>csv</cdr_type>
|
||||
<csv_separator>,</csv_separator>
|
||||
<run_delay>0</run_delay>
|
||||
<cdr_in_dir>/var/log/cgrates/cdrc/in</cdr_in_dir>
|
||||
<cdr_out_dir>/var/log/cgrates/cdrc/out</cdr_out_dir>
|
||||
<cdr_source_id>freeswitch_csv</cdr_source_id>
|
||||
<fields>
|
||||
<field id="accid" value="0;13" />
|
||||
<field id="reqtype" value="1" />
|
||||
<field id="direction" value="2" />
|
||||
<field id="tenant" value="3" />
|
||||
<field id="category" value="4" />
|
||||
<field id="account" value="5" />
|
||||
<field id="subject" value="6" />
|
||||
<field id="destination" value="7" />
|
||||
<field id="setup_time" value="8" />
|
||||
<field id="answer_time" value="9" />
|
||||
<field id="usage" value="10" />
|
||||
<field id="extr1" value="11" />
|
||||
<field id="extr2" value="12" />
|
||||
</fields>
|
||||
</configuration>
|
||||
</document>`
|
||||
var err error
|
||||
reader := strings.NewReader(cfgXmlStr)
|
||||
if cfgDocCdrc, err = ParseCgrXmlConfig(reader); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfgDocCdrc == nil {
|
||||
t.Fatal("Could not parse xml configuration document")
|
||||
}
|
||||
if len(cfgDocCdrc.cdrcs) != 1 {
|
||||
t.Error("Did not cache")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCdrcCfgs(t *testing.T) {
|
||||
cdrcfgs := cfgDocCdrc.GetCdrcCfgs("CDRC-CSV1")
|
||||
if cdrcfgs == nil {
|
||||
t.Error("No config instance returned")
|
||||
}
|
||||
expectCdrc := &CgrXmlCdrcCfg{Enabled: true, CdrsAddress: "internal", CdrType: "csv", CsvSeparator: ",",
|
||||
RunDelay: 0, CdrInDir: "/var/log/cgrates/cdrc/in", CdrOutDir: "/var/log/cgrates/cdrc/out", CdrSourceId: "freeswitch_csv"}
|
||||
cdrFlds := []*CdrcField{
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCID, Value: "0;13"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.REQTYPE, Value: "1"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DIRECTION, Value: "2"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.TENANT, Value: "3"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.CATEGORY, Value: "4"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ACCOUNT, Value: "5"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SUBJECT, Value: "6"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.DESTINATION, Value: "7"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.SETUP_TIME, Value: "8"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.ANSWER_TIME, Value: "9"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: utils.USAGE, Value: "10"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr1", Value: "11"},
|
||||
&CdrcField{XMLName: xml.Name{Local: "field"}, Id: "extr2", Value: "12"}}
|
||||
for _, fld := range cdrFlds {
|
||||
fld.PopulateRSRFields()
|
||||
}
|
||||
expectCdrc.CdrFields = cdrFlds
|
||||
if !reflect.DeepEqual(expectCdrc, cdrcfgs["CDRC-CSV1"]) {
|
||||
t.Errorf("Expecting: %v, received: %v", expectCdrc, cdrcfgs["CDRC-CSV1"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCdrRSRFields(t *testing.T) {
|
||||
cdrcfgs := cfgDocCdrc.GetCdrcCfgs("CDRC-CSV1")
|
||||
if cdrcfgs == nil {
|
||||
t.Error("No config instance returned")
|
||||
}
|
||||
eRSRFields := map[string][]*utils.RSRField{
|
||||
utils.ACCID: []*utils.RSRField{&utils.RSRField{Id: "0"}, &utils.RSRField{Id: "13"}},
|
||||
utils.REQTYPE: []*utils.RSRField{&utils.RSRField{Id: "1"}},
|
||||
utils.DIRECTION: []*utils.RSRField{&utils.RSRField{Id: "2"}},
|
||||
utils.TENANT: []*utils.RSRField{&utils.RSRField{Id: "3"}},
|
||||
utils.CATEGORY: []*utils.RSRField{&utils.RSRField{Id: "4"}},
|
||||
utils.ACCOUNT: []*utils.RSRField{&utils.RSRField{Id: "5"}},
|
||||
utils.SUBJECT: []*utils.RSRField{&utils.RSRField{Id: "6"}},
|
||||
utils.DESTINATION: []*utils.RSRField{&utils.RSRField{Id: "7"}},
|
||||
utils.SETUP_TIME: []*utils.RSRField{&utils.RSRField{Id: "8"}},
|
||||
utils.ANSWER_TIME: []*utils.RSRField{&utils.RSRField{Id: "9"}},
|
||||
utils.USAGE: []*utils.RSRField{&utils.RSRField{Id: "10"}},
|
||||
"extr1": []*utils.RSRField{&utils.RSRField{Id: "11"}},
|
||||
"extr2": []*utils.RSRField{&utils.RSRField{Id: "12"}},
|
||||
}
|
||||
if rsrFields := cdrcfgs["CDRC-CSV1"].CdrRSRFields(); !reflect.DeepEqual(rsrFields, eRSRFields) {
|
||||
t.Errorf("Expecting: %v, received: %v", eRSRFields, rsrFields)
|
||||
}
|
||||
}
|
||||
154
config/xmlcdre.go
Normal file
154
config/xmlcdre.go
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
// The CdrExporter configuration instance
|
||||
type CgrXmlCdreCfg struct {
|
||||
CdrFormat *string `xml:"cdr_format"`
|
||||
FieldSeparator *string `xml:"field_separator"`
|
||||
DataUsageMultiplyFactor *float64 `xml:"data_usage_multiply_factor"`
|
||||
CostMultiplyFactor *float64 `xml:"cost_multiply_factor"`
|
||||
CostRoundingDecimals *int `xml:"cost_rounding_decimals"`
|
||||
CostShiftDigits *int `xml:"cost_shift_digits"`
|
||||
MaskDestId *string `xml:"mask_destination_id"`
|
||||
MaskLength *int `xml:"mask_length"`
|
||||
ExportDir *string `xml:"export_dir"`
|
||||
Header *CgrXmlCfgCdrHeader `xml:"export_template>header"`
|
||||
Content *CgrXmlCfgCdrContent `xml:"export_template>content"`
|
||||
Trailer *CgrXmlCfgCdrTrailer `xml:"export_template>trailer"`
|
||||
}
|
||||
|
||||
func (xmlCdreCfg *CgrXmlCdreCfg) AsCdreConfig() *CdreConfig {
|
||||
cdreCfg, _ := NewDefaultCdreConfig()
|
||||
if xmlCdreCfg.CdrFormat != nil {
|
||||
cdreCfg.CdrFormat = *xmlCdreCfg.CdrFormat
|
||||
}
|
||||
if xmlCdreCfg.FieldSeparator != nil && len(*xmlCdreCfg.FieldSeparator) == 1 {
|
||||
sepStr := *xmlCdreCfg.FieldSeparator
|
||||
cdreCfg.FieldSeparator = rune(sepStr[0])
|
||||
}
|
||||
if xmlCdreCfg.DataUsageMultiplyFactor != nil {
|
||||
cdreCfg.DataUsageMultiplyFactor = *xmlCdreCfg.DataUsageMultiplyFactor
|
||||
}
|
||||
if xmlCdreCfg.CostMultiplyFactor != nil {
|
||||
cdreCfg.CostMultiplyFactor = *xmlCdreCfg.CostMultiplyFactor
|
||||
}
|
||||
if xmlCdreCfg.CostRoundingDecimals != nil {
|
||||
cdreCfg.CostRoundingDecimals = *xmlCdreCfg.CostRoundingDecimals
|
||||
}
|
||||
if xmlCdreCfg.CostShiftDigits != nil {
|
||||
cdreCfg.CostShiftDigits = *xmlCdreCfg.CostShiftDigits
|
||||
}
|
||||
if xmlCdreCfg.MaskDestId != nil {
|
||||
cdreCfg.MaskDestId = *xmlCdreCfg.MaskDestId
|
||||
}
|
||||
if xmlCdreCfg.MaskLength != nil {
|
||||
cdreCfg.MaskLength = *xmlCdreCfg.MaskLength
|
||||
}
|
||||
if xmlCdreCfg.ExportDir != nil {
|
||||
cdreCfg.ExportDir = *xmlCdreCfg.ExportDir
|
||||
}
|
||||
if xmlCdreCfg.Header != nil {
|
||||
cdreCfg.HeaderFields = make([]*CdreCdrField, len(xmlCdreCfg.Header.Fields))
|
||||
for idx, xmlFld := range xmlCdreCfg.Header.Fields {
|
||||
cdreCfg.HeaderFields[idx] = xmlFld.AsCdreCdrField()
|
||||
}
|
||||
}
|
||||
if xmlCdreCfg.Content != nil {
|
||||
cdreCfg.ContentFields = make([]*CdreCdrField, len(xmlCdreCfg.Content.Fields))
|
||||
for idx, xmlFld := range xmlCdreCfg.Content.Fields {
|
||||
cdreCfg.ContentFields[idx] = xmlFld.AsCdreCdrField()
|
||||
}
|
||||
}
|
||||
if xmlCdreCfg.Trailer != nil {
|
||||
cdreCfg.TrailerFields = make([]*CdreCdrField, len(xmlCdreCfg.Trailer.Fields))
|
||||
for idx, xmlFld := range xmlCdreCfg.Trailer.Fields {
|
||||
cdreCfg.TrailerFields[idx] = xmlFld.AsCdreCdrField()
|
||||
}
|
||||
}
|
||||
return cdreCfg
|
||||
}
|
||||
|
||||
// CDR header
|
||||
type CgrXmlCfgCdrHeader struct {
|
||||
XMLName xml.Name `xml:"header"`
|
||||
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// CDR content
|
||||
type CgrXmlCfgCdrContent struct {
|
||||
XMLName xml.Name `xml:"content"`
|
||||
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// CDR trailer
|
||||
type CgrXmlCfgCdrTrailer struct {
|
||||
XMLName xml.Name `xml:"trailer"`
|
||||
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// CDR field
|
||||
type CgrXmlCfgCdrField struct {
|
||||
XMLName xml.Name `xml:"field"`
|
||||
Name string `xml:"name,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
Width int `xml:"width,attr"` // Field width
|
||||
Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright>
|
||||
Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right>
|
||||
Layout string `xml:"layout,attr"` // Eg. time format layout
|
||||
Filter string `xml:"filter,attr"` // Eg. combimed filters
|
||||
Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported
|
||||
valueAsRsrField *utils.RSRField // Cached if the need arrises
|
||||
filterAsRsrField *utils.RSRField
|
||||
}
|
||||
|
||||
func (cdrFld *CgrXmlCfgCdrField) populateRSRField() (err error) {
|
||||
cdrFld.valueAsRsrField, err = utils.NewRSRField(cdrFld.Value)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cdrFld *CgrXmlCfgCdrField) populateFltrRSRField() (err error) {
|
||||
cdrFld.filterAsRsrField, err = utils.NewRSRField(cdrFld.Filter)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cdrFld *CgrXmlCfgCdrField) ValueAsRSRField() *utils.RSRField {
|
||||
return cdrFld.valueAsRsrField
|
||||
}
|
||||
|
||||
func (cdrFld *CgrXmlCfgCdrField) AsCdreCdrField() *CdreCdrField {
|
||||
return &CdreCdrField{
|
||||
Name: cdrFld.Name,
|
||||
Type: cdrFld.Type,
|
||||
Value: cdrFld.Value,
|
||||
Width: cdrFld.Width,
|
||||
Strip: cdrFld.Strip,
|
||||
Padding: cdrFld.Padding,
|
||||
Layout: cdrFld.Layout,
|
||||
Filter: cdrFld.filterAsRsrField,
|
||||
Mandatory: cdrFld.Mandatory,
|
||||
valueAsRsrField: cdrFld.valueAsRsrField,
|
||||
}
|
||||
}
|
||||
306
config/xmlcdre_test.go
Normal file
306
config/xmlcdre_test.go
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cfgDoc *CgrXmlCfgDocument // Will be populated by first test
|
||||
|
||||
func TestXmlCdreCfgPopulateCdreRSRFIeld(t *testing.T) {
|
||||
cdreField := CgrXmlCfgCdrField{Name: "TEST1", Type: "cdrfield", Value: `~effective_caller_id_number:s/(\d+)/+$1/`}
|
||||
if err := cdreField.populateRSRField(); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if cdreField.valueAsRsrField == nil {
|
||||
t.Error("Failed loading the RSRField")
|
||||
}
|
||||
valRSRField, _ := utils.NewRSRField(`~effective_caller_id_number:s/(\d+)/+$1/`)
|
||||
if recv := cdreField.ValueAsRSRField(); !reflect.DeepEqual(valRSRField, recv) {
|
||||
t.Errorf("Expecting %v, received %v", valRSRField, recv)
|
||||
}
|
||||
/*cdreField = CgrXmlCfgCdrField{Name: "TEST1", Type: "constant", Value: `someval`}
|
||||
if err := cdreField.populateRSRField(); err != nil {
|
||||
t.Error("Unexpected error: ", err.Error())
|
||||
} else if cdreField.valueAsRsrField != nil {
|
||||
t.Error("Should not load the RSRField")
|
||||
}*/
|
||||
}
|
||||
|
||||
func TestXmlCdreCfgParseXmlConfig(t *testing.T) {
|
||||
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdre" id="CDRE-FW1">
|
||||
<cdr_format>fwv</cdr_format>
|
||||
<data_usage_multiply_factor>0.0</data_usage_multiply_factor>
|
||||
<cost_multiply_factor>0.0</cost_multiply_factor>
|
||||
<cost_rounding_decimals>-1</cost_rounding_decimals>
|
||||
<cost_shift_digits>0</cost_shift_digits>
|
||||
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
|
||||
<mask_length>0</mask_length>
|
||||
<export_dir>/var/log/cgrates/cdre</export_dir>
|
||||
<export_template>
|
||||
<header>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="10" width="2" />
|
||||
<field name="Filler1" type="filler" width="3" />
|
||||
<field name="DistributorCode" type="constant" value="VOI" width="3" />
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
|
||||
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
|
||||
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150400" width="12" />
|
||||
<field name="Version" type="constant" value="01" width="2" />
|
||||
<field name="Filler2" type="filler" width="105" />
|
||||
</fields>
|
||||
</header>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="20" width="2" />
|
||||
<field name="Account" type="cdrfield" value="cgrid" width="12" mandatory="true" />
|
||||
<field name="Subject" type="cdrfield" value="subject" strip="left" padding="left" width="5" />
|
||||
<field name="CLI" type="cdrfield" value="cli" strip="xright" width="15" />
|
||||
<field name="Destination" type="cdrfield" value="destination" strip="xright" width="24" />
|
||||
<field name="TOR" type="constant" value="02" width="2" />
|
||||
<field name="SubtypeTOR" type="constant" value="11" width="4" />
|
||||
<field name="SetupTime" type="cdrfield" value="start_time" layout="020106150400" width="12" />
|
||||
<field name="Duration" type="cdrfield" value="duration" width="6" multiply_factor_voice="1000" />
|
||||
<field name="DataVolume" type="filler" width="6" />
|
||||
<field name="TaxCode" type="constant" value="1" width="1" />
|
||||
<field name="OperatorCode" type="cdrfield" value="operator" width="2" />
|
||||
<field name="ProductId" type="cdrfield" value="productid" width="5" />
|
||||
<field name="NetworkId" type="constant" value="3" width="1" />
|
||||
<field name="CallId" type="cdrfield" value="accid" width="16" />
|
||||
<field name="Filler" type="filler" width="8" />
|
||||
<field name="Filler" type="filler" width="8" />
|
||||
<field name="TerminationCode" type="cdrfield" value="~cost_details:s/"MatchedDestId":".+_(\s\s\s\s\s)"/$1/" width="5" />
|
||||
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
|
||||
<field name="CalledMask" type="cdrfield" value="calledmask" width="1" />
|
||||
</fields>
|
||||
</content>
|
||||
<trailer>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="90" width="2" />
|
||||
<field name="Filler1" type="filler" width="3" />
|
||||
<field name="DistributorCode" type="constant" value="VOI" width="3" />
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
|
||||
<field name="NumberOfRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
|
||||
<field name="CdrsDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
|
||||
<field name="FirstCdrTime" type="metatag" value="first_cdr_time" layout="020106150400" width="12" />
|
||||
<field name="LastCdrTime" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
|
||||
<field name="Filler1" type="filler" width="93" />
|
||||
</fields>
|
||||
</trailer>
|
||||
</export_template>
|
||||
</configuration>
|
||||
<configuration section="cdre" type="csv" id="CHECK-CSV1">
|
||||
<export_template>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="CGRID" type="cdrfield" value="cgrid" width="40"/>
|
||||
<field name="RatingSubject" type="cdrfield" value="subject" width="24" padding="left" strip="xright" mandatory="true"/>
|
||||
<field name="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true"/>
|
||||
<field name="AccountReference" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
|
||||
<field name="AccountType" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" mandatory="true" />
|
||||
<field name="MultipleMed1" type="combimed" value="cost" strip="xright" padding="left" mandatory="true" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
|
||||
</fields>
|
||||
</content>
|
||||
</export_template>
|
||||
</configuration>
|
||||
|
||||
</document>`
|
||||
var err error
|
||||
reader := strings.NewReader(cfgXmlStr)
|
||||
if cfgDoc, err = ParseCgrXmlConfig(reader); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfgDoc == nil {
|
||||
t.Fatal("Could not parse xml configuration document")
|
||||
}
|
||||
if len(cfgDoc.cdres) != 2 {
|
||||
t.Error("Did not cache")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlCdreCfgGetCdreCfg(t *testing.T) {
|
||||
cdreFWCfg := cfgDoc.GetCdreCfgs("CDRE-FW1")
|
||||
if cdreFWCfg == nil {
|
||||
t.Error("Could not parse CdreFw instance")
|
||||
}
|
||||
if len(cdreFWCfg["CDRE-FW1"].Header.Fields) != 8 {
|
||||
t.Error("Unexpected number of header fields parsed", len(cdreFWCfg["CDRE-FW1"].Header.Fields))
|
||||
}
|
||||
if len(cdreFWCfg["CDRE-FW1"].Content.Fields) != 20 {
|
||||
t.Error("Unexpected number of content fields parsed", len(cdreFWCfg["CDRE-FW1"].Content.Fields))
|
||||
}
|
||||
if len(cdreFWCfg["CDRE-FW1"].Trailer.Fields) != 9 {
|
||||
t.Error("Unexpected number of trailer fields parsed", len(cdreFWCfg["CDRE-FW1"].Trailer.Fields))
|
||||
}
|
||||
cdreCsvCfg1 := cfgDoc.GetCdreCfgs("CHECK-CSV1")
|
||||
if cdreCsvCfg1 == nil {
|
||||
t.Error("Could not parse CdreFw instance")
|
||||
}
|
||||
if len(cdreCsvCfg1["CHECK-CSV1"].Content.Fields) != 6 {
|
||||
t.Error("Unexpected number of content fields parsed", len(cdreCsvCfg1["CHECK-CSV1"].Content.Fields))
|
||||
}
|
||||
}
|
||||
|
||||
func TestXmlCdreCfgAsCdreConfig(t *testing.T) {
|
||||
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdre" type="fixed_width" id="CDRE-FW2">
|
||||
<cdr_format>fwv</cdr_format>
|
||||
<field_separator>;</field_separator>
|
||||
<data_usage_multiply_factor>1024.0</data_usage_multiply_factor>
|
||||
<cost_multiply_factor>1.19</cost_multiply_factor>
|
||||
<cost_rounding_decimals>-1</cost_rounding_decimals>
|
||||
<cost_shift_digits>-3</cost_shift_digits>
|
||||
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
|
||||
<mask_length>1</mask_length>
|
||||
<export_dir>/var/log/cgrates/cdre</export_dir>
|
||||
<export_template>
|
||||
<header>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="10" width="2" />
|
||||
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12" />
|
||||
</fields>
|
||||
</header>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="OperatorCode" type="cdrfield" value="operator" width="2" />
|
||||
<field name="ProductId" type="cdrfield" value="productid" width="5" />
|
||||
<field name="NetworkId" type="constant" value="3" width="1" />
|
||||
<field name="FromHttpPost1" type="http_post" value="https://localhost:8000" width="10" strip="xright" padding="left" />
|
||||
<field name="CombiMed1" type="combimed" value="cost" width="10" strip="xright" padding="left" filter="~mediation_runid:s/DEFAULT/SECOND_RUN/"/>
|
||||
</fields>
|
||||
</content>
|
||||
<trailer>
|
||||
<fields>
|
||||
<field name="DistributorCode" type="constant" value="VOI" width="3" />
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
|
||||
</fields>
|
||||
</trailer>
|
||||
</export_template>
|
||||
</configuration>
|
||||
</document>`
|
||||
var err error
|
||||
reader := strings.NewReader(cfgXmlStr)
|
||||
if cfgDoc, err = ParseCgrXmlConfig(reader); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfgDoc == nil {
|
||||
t.Fatal("Could not parse xml configuration document")
|
||||
}
|
||||
xmlCdreCfgs := cfgDoc.GetCdreCfgs("CDRE-FW2")
|
||||
if xmlCdreCfgs == nil {
|
||||
t.Error("Could not parse XmlCdre instance")
|
||||
}
|
||||
eCdreCfg := &CdreConfig{
|
||||
CdrFormat: "fwv",
|
||||
FieldSeparator: ';',
|
||||
DataUsageMultiplyFactor: 1024.0,
|
||||
CostMultiplyFactor: 1.19,
|
||||
CostRoundingDecimals: -1,
|
||||
CostShiftDigits: -3,
|
||||
MaskDestId: "MASKED_DESTINATIONS",
|
||||
MaskLength: 1,
|
||||
ExportDir: "/var/log/cgrates/cdre",
|
||||
}
|
||||
fltrCombiMed, _ := utils.NewRSRField("~mediation_runid:s/DEFAULT/SECOND_RUN/")
|
||||
eCdreCfg.HeaderFields = []*CdreCdrField{
|
||||
&CdreCdrField{
|
||||
Name: "TypeOfRecord",
|
||||
Type: "constant",
|
||||
Value: "10",
|
||||
Width: 2,
|
||||
valueAsRsrField: &utils.RSRField{Id: "10"}},
|
||||
&CdreCdrField{
|
||||
Name: "LastCdr",
|
||||
Type: "metatag",
|
||||
Value: "last_cdr_time",
|
||||
Layout: "020106150400",
|
||||
Width: 12,
|
||||
valueAsRsrField: &utils.RSRField{Id: "last_cdr_time"}},
|
||||
}
|
||||
eCdreCfg.ContentFields = []*CdreCdrField{
|
||||
&CdreCdrField{
|
||||
Name: "OperatorCode",
|
||||
Type: "cdrfield",
|
||||
Value: "operator",
|
||||
Width: 2,
|
||||
valueAsRsrField: &utils.RSRField{Id: "operator"},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: "ProductId",
|
||||
Type: "cdrfield",
|
||||
Value: "productid",
|
||||
Width: 5,
|
||||
valueAsRsrField: &utils.RSRField{Id: "productid"},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: "NetworkId",
|
||||
Type: "constant",
|
||||
Value: "3",
|
||||
Width: 1,
|
||||
valueAsRsrField: &utils.RSRField{Id: "3"},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: "FromHttpPost1",
|
||||
Type: "http_post",
|
||||
Value: "https://localhost:8000",
|
||||
Width: 10,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
valueAsRsrField: &utils.RSRField{Id: "https://localhost:8000"},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: "CombiMed1",
|
||||
Type: "combimed",
|
||||
Value: "cost",
|
||||
Width: 10,
|
||||
Strip: "xright",
|
||||
Padding: "left",
|
||||
Filter: fltrCombiMed,
|
||||
valueAsRsrField: &utils.RSRField{Id: "cost"},
|
||||
},
|
||||
}
|
||||
eCdreCfg.TrailerFields = []*CdreCdrField{
|
||||
&CdreCdrField{
|
||||
Name: "DistributorCode",
|
||||
Type: "constant",
|
||||
Value: "VOI",
|
||||
Width: 3,
|
||||
valueAsRsrField: &utils.RSRField{Id: "VOI"},
|
||||
},
|
||||
&CdreCdrField{
|
||||
Name: "FileSeqNr",
|
||||
Type: "metatag",
|
||||
Value: "export_id",
|
||||
Width: 5,
|
||||
Padding: "zeroleft",
|
||||
valueAsRsrField: &utils.RSRField{Id: "export_id"},
|
||||
},
|
||||
}
|
||||
if rcvCdreCfg := xmlCdreCfgs["CDRE-FW2"].AsCdreConfig(); !reflect.DeepEqual(rcvCdreCfg, eCdreCfg) {
|
||||
for _, fld := range rcvCdreCfg.ContentFields {
|
||||
fmt.Printf("Fld: %+v\n", fld)
|
||||
}
|
||||
t.Errorf("Expecting: %v, received: %v", eCdreCfg, rcvCdreCfg)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -32,92 +32,140 @@ func ParseCgrXmlConfig(reader io.Reader) (*CgrXmlCfgDocument, error) {
|
||||
if err := decoder.Decode(xmlConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := xmlConfig.cacheAll(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return xmlConfig, nil
|
||||
}
|
||||
|
||||
// Define a format for configuration file, one doc contains more configuration instances, identified by section, type and id
|
||||
type CgrXmlCfgDocument struct {
|
||||
XMLName xml.Name `xml:"document"`
|
||||
Type string `xml:"type,attr"`
|
||||
Configurations []*CgrXmlConfiguration `xml:"configuration"`
|
||||
cdrefws map[string]*CgrXmlCdreFwCfg // Cache for processed fixed width config instances, key will be the id of the instance
|
||||
XMLName xml.Name `xml:"document"`
|
||||
Type string `xml:"type,attr"`
|
||||
Configurations []*CgrXmlConfiguration `xml:"configuration"`
|
||||
cdrcs map[string]*CgrXmlCdrcCfg
|
||||
cdres map[string]*CgrXmlCdreCfg // Cahe cdrexporter instances, key will be the ID
|
||||
}
|
||||
|
||||
// Storage for raw configuration
|
||||
type CgrXmlConfiguration struct {
|
||||
XMLName xml.Name `xml:"configuration"`
|
||||
Section string `xml:"section,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Id string `xml:"id,attr"`
|
||||
RawConfig []byte `xml:",innerxml"` // Used to store the configuration struct, as raw so we can store different types
|
||||
}
|
||||
|
||||
// The CdrExporter Fixed Width configuration instance
|
||||
type CgrXmlCdreFwCfg struct {
|
||||
Header *CgrXmlCfgCdrHeader `xml:"header"`
|
||||
Content *CgrXmlCfgCdrContent `xml:"content"`
|
||||
Trailer *CgrXmlCfgCdrTrailer `xml:"trailer"`
|
||||
func (cfgInst *CgrXmlConfiguration) rawConfigElement() []byte {
|
||||
rawConfig := append([]byte("<element>"), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall into one struct
|
||||
rawConfig = append(rawConfig, []byte("</element>")...)
|
||||
return rawConfig
|
||||
}
|
||||
|
||||
// CDR header
|
||||
type CgrXmlCfgCdrHeader struct {
|
||||
XMLName xml.Name `xml:"header"`
|
||||
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// CDR content
|
||||
type CgrXmlCfgCdrContent struct {
|
||||
XMLName xml.Name `xml:"content"`
|
||||
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// CDR trailer
|
||||
type CgrXmlCfgCdrTrailer struct {
|
||||
XMLName xml.Name `xml:"trailer"`
|
||||
Fields []*CgrXmlCfgCdrField `xml:"fields>field"`
|
||||
}
|
||||
|
||||
// CDR field
|
||||
type CgrXmlCfgCdrField struct {
|
||||
XMLName xml.Name `xml:"field"`
|
||||
Name string `xml:"name,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
Width int `xml:"width,attr"` // Field width
|
||||
Strip string `xml:"strip,attr"` // Strip strategy in case value is bigger than field width <""|left|xleft|right|xright>
|
||||
Padding string `xml:"padding,attr"` // Padding strategy in case of value is smaller than width <""left|zeroleft|right>
|
||||
Layout string `xml:"layout,attr"` // Eg. time format layout
|
||||
Mandatory bool `xml:"mandatory,attr"` // If field is mandatory, empty value will be considered as error and CDR will not be exported
|
||||
}
|
||||
|
||||
// Avoid building from raw config string always, so build cache here
|
||||
func (xmlCfg *CgrXmlCfgDocument) cacheCdreFWCfgs() error {
|
||||
xmlCfg.cdrefws = make(map[string]*CgrXmlCdreFwCfg)
|
||||
for _, cfgInst := range xmlCfg.Configurations {
|
||||
if cfgInst.Section == utils.CDRE || cfgInst.Type == utils.CDRE_FIXED_WIDTH {
|
||||
cdrefwCfg := new(CgrXmlCdreFwCfg)
|
||||
rawConfig := append([]byte("<element>"), cfgInst.RawConfig...) // Encapsulate the rawConfig in one element so we can Unmarshall into one struct
|
||||
rawConfig = append(rawConfig, []byte("</element>")...)
|
||||
if err := xml.Unmarshal(rawConfig, cdrefwCfg); err != nil {
|
||||
return err
|
||||
} else if cdrefwCfg == nil {
|
||||
return fmt.Errorf("Could not unmarshal CgrXmlCdreFwCfg: %s", cfgInst.Id)
|
||||
} else { // All good, cache the config instance
|
||||
xmlCfg.cdrefws[cfgInst.Id] = cdrefwCfg
|
||||
}
|
||||
func (xmlCfg *CgrXmlCfgDocument) cacheAll() error {
|
||||
for _, cacheFunc := range []func() error{xmlCfg.cacheCdrcCfgs, xmlCfg.cacheCdreCfgs} {
|
||||
if err := cacheFunc(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (xmlCfg *CgrXmlCfgDocument) GetCdreFWCfg(instName string) (*CgrXmlCdreFwCfg, error) {
|
||||
if len(xmlCfg.cdrefws) == 0 { // First time, cache also
|
||||
if err := xmlCfg.cacheCdreFWCfgs(); err != nil {
|
||||
return nil, err
|
||||
// Avoid building from raw config string always, so build cache here
|
||||
func (xmlCfg *CgrXmlCfgDocument) cacheCdrcCfgs() error {
|
||||
xmlCfg.cdrcs = make(map[string]*CgrXmlCdrcCfg)
|
||||
for _, cfgInst := range xmlCfg.Configurations {
|
||||
if cfgInst.Section != utils.CDRC {
|
||||
continue // Another type of config instance, not interesting to process
|
||||
}
|
||||
cdrcCfg := new(CgrXmlCdrcCfg)
|
||||
if err := xml.Unmarshal(cfgInst.rawConfigElement(), cdrcCfg); err != nil {
|
||||
return err
|
||||
} else if cdrcCfg == nil {
|
||||
return fmt.Errorf("Could not unmarshal config instance: %s", cfgInst.Id)
|
||||
}
|
||||
// Cache rsr fields
|
||||
for _, fld := range cdrcCfg.CdrFields {
|
||||
if err := fld.PopulateRSRFields(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Id, err.Error())
|
||||
}
|
||||
}
|
||||
cdrcCfg.setDefaults()
|
||||
xmlCfg.cdrcs[cfgInst.Id] = cdrcCfg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid building from raw config string always, so build cache here
|
||||
func (xmlCfg *CgrXmlCfgDocument) cacheCdreCfgs() error {
|
||||
xmlCfg.cdres = make(map[string]*CgrXmlCdreCfg)
|
||||
for _, cfgInst := range xmlCfg.Configurations {
|
||||
if cfgInst.Section != utils.CDRE {
|
||||
continue
|
||||
}
|
||||
cdreCfg := new(CgrXmlCdreCfg)
|
||||
if err := xml.Unmarshal(cfgInst.rawConfigElement(), cdreCfg); err != nil {
|
||||
return err
|
||||
} else if cdreCfg == nil {
|
||||
return fmt.Errorf("Could not unmarshal CgrXmlCdreCfg: %s", cfgInst.Id)
|
||||
}
|
||||
if cdreCfg.Header != nil {
|
||||
// Cache rsr fields
|
||||
for _, fld := range cdreCfg.Header.Fields {
|
||||
if err := fld.populateRSRField(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
|
||||
}
|
||||
if err := fld.populateFltrRSRField(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if cdreCfg.Content != nil {
|
||||
// Cache rsr fields
|
||||
for _, fld := range cdreCfg.Content.Fields {
|
||||
if err := fld.populateRSRField(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
|
||||
}
|
||||
if err := fld.populateFltrRSRField(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if cdreCfg.Trailer != nil {
|
||||
// Cache rsr fields
|
||||
for _, fld := range cdreCfg.Trailer.Fields {
|
||||
if err := fld.populateRSRField(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
|
||||
}
|
||||
if err := fld.populateFltrRSRField(); err != nil {
|
||||
return fmt.Errorf("Populating field %s, error: %s", fld.Name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlCfg.cdres[cfgInst.Id] = cdreCfg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return instances or filtered instance of cdrefw configuration
|
||||
func (xmlCfg *CgrXmlCfgDocument) GetCdreCfgs(instName string) map[string]*CgrXmlCdreCfg {
|
||||
if len(instName) != 0 {
|
||||
if cfg, hasIt := xmlCfg.cdres[instName]; !hasIt {
|
||||
return nil
|
||||
} else {
|
||||
return map[string]*CgrXmlCdreCfg{instName: cfg}
|
||||
}
|
||||
}
|
||||
if cfg, hasIt := xmlCfg.cdrefws[instName]; hasIt {
|
||||
return cfg, nil
|
||||
}
|
||||
return nil, nil
|
||||
return xmlCfg.cdres
|
||||
}
|
||||
|
||||
// Return instances or filtered instance of cdrc configuration
|
||||
func (xmlCfg *CgrXmlCfgDocument) GetCdrcCfgs(instName string) map[string]*CgrXmlCdrcCfg {
|
||||
if len(instName) != 0 {
|
||||
if cfg, hasIt := xmlCfg.cdrcs[instName]; !hasIt {
|
||||
return nil
|
||||
} else {
|
||||
return map[string]*CgrXmlCdrcCfg{instName: cfg} // Filtered
|
||||
}
|
||||
}
|
||||
return xmlCfg.cdrcs // Unfiltered
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cfgDoc *CgrXmlCfgDocument // Will be populated by first test
|
||||
|
||||
func TestParseXmlConfig(t *testing.T) {
|
||||
cfgXmlStr := `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdre" type="fixed_width" id="CDRE-FW1">
|
||||
<header>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="10" width="2"/>
|
||||
<field name="Filler1" type="filler" width="3"/>
|
||||
<field name="DistributorCode" type="constant" value="VOI" width="3"/>
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5"/>
|
||||
<field name="LastCdr" type="metatag" value="last_cdr_time" layout="020106150400" width="12"/>
|
||||
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150400" width="12"/>
|
||||
<field name="Version" type="constant" value="01" width="2"/>
|
||||
<field name="Filler2" type="filler" width="105"/>
|
||||
</fields>
|
||||
</header>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="20" width="2"/>
|
||||
<field name="Account" type="cdrfield" value="cgrid" width="12" mandatory="true"/>
|
||||
<field name="Subject" type="cdrfield" value="subject" strip="left" padding="left" width="5"/>
|
||||
<field name="CLI" type="cdrfield" value="cli" strip="xright" width="15"/>
|
||||
<field name="Destination" type="cdrfield" value="destination" strip="xright" width="24"/>
|
||||
<field name="TOR" type="constant" value="02" width="2"/>
|
||||
<field name="SubtypeTOR" type="constant" value="11" width="4"/>
|
||||
<field name="SetupTime" type="cdrfield" value="start_time" layout="020106150400" width="12"/>
|
||||
<field name="Duration" type="cdrfield" value="duration" width="6"/>
|
||||
<field name="DataVolume" type="filler" width="6"/>
|
||||
<field name="TaxCode" type="constant" value="1" width="1"/>
|
||||
<field name="OperatorCode" type="cdrfield" value="operator" width="2"/>
|
||||
<field name="ProductId" type="cdrfield" value="productid" width="5"/>
|
||||
<field name="NetworkId" type="constant" value="3" width="1"/>
|
||||
<field name="CallId" type="cdrfield" value="accid" width="16"/>
|
||||
<field name="Filler" type="filler" width="8"/>
|
||||
<field name="Filler" type="filler" width="8"/>
|
||||
<field name="TerminationCode" type="concatenated_cdrfield" value="operator,product" width="5"/>
|
||||
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9"/>
|
||||
<field name="CalledMask" type="cdrfield" value="calledmask" width="1"/>
|
||||
</fields>
|
||||
</content>
|
||||
<trailer>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="90" width="2"/>
|
||||
<field name="Filler1" type="filler" width="3"/>
|
||||
<field name="DistributorCode" type="constant" value="VOI" width="3"/>
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5"/>
|
||||
<field name="NumberOfRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6"/>
|
||||
<field name="CdrsDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8"/>
|
||||
<field name="FirstCdrTime" type="metatag" value="first_cdr_time" layout="020106150400" width="12"/>
|
||||
<field name="LastCdrTime" type="metatag" value="last_cdr_time" layout="020106150400" width="12"/>
|
||||
<field name="Filler1" type="filler" width="93"/>
|
||||
</fields>
|
||||
</trailer>
|
||||
</configuration>
|
||||
</document>`
|
||||
var err error
|
||||
reader := strings.NewReader(cfgXmlStr)
|
||||
if cfgDoc, err = ParseCgrXmlConfig(reader); err != nil {
|
||||
t.Error(err.Error())
|
||||
} else if cfgDoc == nil {
|
||||
t.Fatal("Could not parse xml configuration document")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheCdreFWCfgs(t *testing.T) {
|
||||
if len(cfgDoc.cdrefws) != 0 {
|
||||
t.Error("Cache should be empty before caching")
|
||||
}
|
||||
if err := cfgDoc.cacheCdreFWCfgs(); err != nil {
|
||||
t.Error(err)
|
||||
} else if len(cfgDoc.cdrefws) != 1 {
|
||||
t.Error("Did not cache")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCdreFWCfg(t *testing.T) {
|
||||
cdreFWCfg, err := cfgDoc.GetCdreFWCfg("CDRE-FW1")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if cdreFWCfg == nil {
|
||||
t.Error("Could not parse CdreFw instance")
|
||||
}
|
||||
if len(cdreFWCfg.Header.Fields) != 8 {
|
||||
t.Error("Unexpected number of header fields parsed", len(cdreFWCfg.Header.Fields))
|
||||
}
|
||||
if len(cdreFWCfg.Content.Fields) != 20 {
|
||||
t.Error("Unexpected number of content fields parsed", len(cdreFWCfg.Content.Fields))
|
||||
}
|
||||
if len(cdreFWCfg.Trailer.Fields) != 9 {
|
||||
t.Error("Unexpected number of trailer fields parsed", len(cdreFWCfg.Trailer.Fields))
|
||||
}
|
||||
}
|
||||
@@ -19,46 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["get_account"] = &CmdGetAccount{}
|
||||
c := &CmdGetAccount{
|
||||
name: "account",
|
||||
rpcMethod: "ApierV1.GetAccount",
|
||||
rpcParams: &utils.AttrGetAccount{Direction: "*out"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetAccount struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrGetAccount
|
||||
rpcResult *engine.Account
|
||||
rpcParams *utils.AttrGetAccount
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetAccount) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_account <tenant> <account>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetAccount) defaults() error {
|
||||
self.rpcMethod = "ApierV1.GetAccount"
|
||||
self.rpcParams = &apier.AttrGetAccount{BalanceType: engine.CREDIT}
|
||||
self.rpcParams.Direction = "*out"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdGetAccount) FromArgs(args []string) error {
|
||||
if len(args) < 4 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.Tenant = args[2]
|
||||
self.rpcParams.Account = args[3]
|
||||
return nil
|
||||
func (self *CmdGetAccount) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetAccount) RpcMethod() string {
|
||||
@@ -66,9 +50,12 @@ func (self *CmdGetAccount) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdGetAccount) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.AttrGetAccount{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetAccount) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
return &engine.Account{}
|
||||
}
|
||||
58
console/account_add.go
Normal file
58
console/account_add.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/apier"
|
||||
|
||||
func init() {
|
||||
c := &CmdAddAccount{
|
||||
name: "account_add",
|
||||
rpcMethod: "ApierV1.SetAccount",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdAddAccount struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrSetAccount
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrSetAccount{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) RpcResult() interface{} {
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -18,42 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
commands["set_accountactions"] = &CmdSetAccountActions{}
|
||||
c := &CmdSetAccountActions{
|
||||
name: "accountactions_set",
|
||||
rpcMethod: "ApierV1.SetAccountActions",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdSetAccountActions struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.TPAccountActions
|
||||
rpcResult string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdSetAccountActions) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] set_accountactions <tpid> <loadid> <tenant> <account>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdSetAccountActions) defaults() error {
|
||||
self.rpcMethod = "ApierV1.SetAccountActions"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdSetAccountActions) FromArgs(args []string) error {
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams = &utils.TPAccountActions{TPid: args[2], LoadId: args[3], Tenant: args[4], Account: args[5], Direction: "*out"}
|
||||
return nil
|
||||
func (self *CmdSetAccountActions) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdSetAccountActions) RpcMethod() string {
|
||||
@@ -61,9 +46,13 @@ func (self *CmdSetAccountActions) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdSetAccountActions) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.TPAccountActions{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdSetAccountActions) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -18,48 +18,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/apier"
|
||||
|
||||
func init() {
|
||||
commands["execute_action"] = &CmdExecuteAction{}
|
||||
c := &CmdExecuteAction{
|
||||
name: "action_execute",
|
||||
rpcMethod: "ApierV1.ExecuteAction",
|
||||
rpcParams: &apier.AttrExecuteAction{Direction: "*out"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdExecuteAction struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrExecuteAction
|
||||
rpcResult string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdExecuteAction) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] execute_action <tenant> <account> <actionsid> [<direction>]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdExecuteAction) defaults() error {
|
||||
self.rpcMethod = "ApierV1.ExecuteAction"
|
||||
self.rpcParams = &apier.AttrExecuteAction{Direction: "*out"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdExecuteAction) FromArgs(args []string) error {
|
||||
if len(args) < 5 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.Tenant = args[2]
|
||||
self.rpcParams.Account = args[3]
|
||||
self.rpcParams.ActionsId = args[4]
|
||||
if len(args) > 5 {
|
||||
self.rpcParams.Direction = args[5]
|
||||
}
|
||||
return nil
|
||||
func (self *CmdExecuteAction) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdExecuteAction) RpcMethod() string {
|
||||
@@ -67,9 +47,13 @@ func (self *CmdExecuteAction) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdExecuteAction) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrExecuteAction{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdExecuteAction) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["add_account"] = &CmdAddAccount{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdAddAccount struct {
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrSetAccount
|
||||
rpcResult string
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdAddAccount) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] add_account <tenant> <account> <allownegative> <actiontimingsid> [<direction>]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdAddAccount) defaults() error {
|
||||
self.rpcMethod = "ApierV1.SetAccount"
|
||||
self.rpcParams = &apier.AttrSetAccount{Direction: "*out"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdAddAccount) FromArgs(args []string) error {
|
||||
if len(args) < 6 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.Tenant = args[2]
|
||||
self.rpcParams.Account = args[3]
|
||||
if an, err := strconv.ParseBool(args[4]); err != nil {
|
||||
return fmt.Errorf("Error parsing allownegative boolean: ", args[4])
|
||||
} else {
|
||||
self.rpcParams.AllowNegative = an
|
||||
}
|
||||
self.rpcParams.ActionPlanId = args[5]
|
||||
if len(args) > 6 {
|
||||
self.rpcParams.Direction = args[6]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdAddAccount) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["add_balance"] = &CmdAddBalance{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdAddBalance struct {
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrAddBalance
|
||||
rpcResult string
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdAddBalance) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] add_balance <tenant> <account> <value> [*monetary|*sms|*internet|*internet_time|*minutes [<destinationid> [<weight> [overwrite]]]]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdAddBalance) defaults() error {
|
||||
self.rpcMethod = "ApierV1.AddBalance"
|
||||
self.rpcParams = &apier.AttrAddBalance{BalanceType: engine.CREDIT}
|
||||
self.rpcParams.Direction = "*out"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdAddBalance) FromArgs(args []string) error {
|
||||
var err error
|
||||
if len(args) < 5 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.Tenant = args[2]
|
||||
self.rpcParams.Account = args[3]
|
||||
value, err := strconv.ParseFloat(args[4], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.rpcParams.Value = value
|
||||
if len(args) > 5 {
|
||||
self.rpcParams.BalanceType = args[5]
|
||||
}
|
||||
if len(args) > 6 {
|
||||
self.rpcParams.DestinationId = args[6]
|
||||
}
|
||||
if len(args) > 7 {
|
||||
if self.rpcParams.Weight, err = strconv.ParseFloat(args[7], 64); err != nil {
|
||||
return fmt.Errorf("Cannot parse weight parameter")
|
||||
}
|
||||
}
|
||||
if len(args) > 8 {
|
||||
if args[8] == "overwrite" {
|
||||
self.rpcParams.Overwrite = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["add_triggeredaction"] = &CmdAddTriggeredAction{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdAddTriggeredAction struct {
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrAddActionTrigger
|
||||
rpcResult string
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdAddTriggeredAction) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] add_triggeredaction <tenant> <account> <balanceid> <thresholdvalue> <destinationid> <weight> <actionsid> [<direction>]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdAddTriggeredAction) defaults() error {
|
||||
self.rpcMethod = "ApierV1.AddTriggeredAction"
|
||||
self.rpcParams = &apier.AttrAddActionTrigger{Direction: "*out"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdAddTriggeredAction) FromArgs(args []string) error {
|
||||
if len(args) < 9 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.Tenant = args[2]
|
||||
self.rpcParams.Account = args[3]
|
||||
self.rpcParams.BalanceType = args[4]
|
||||
thresholdvalue, err := strconv.ParseFloat(args[5], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.rpcParams.ThresholdValue = thresholdvalue
|
||||
self.rpcParams.DestinationId = args[6]
|
||||
weight, err := strconv.ParseFloat(args[7], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.rpcParams.Weight = weight
|
||||
self.rpcParams.ActionsId = args[8]
|
||||
|
||||
if len(args) > 9 {
|
||||
self.rpcParams.Direction = args[9]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
64
console/balance_debit.go
Normal file
64
console/balance_debit.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 "github.com/cgrates/cgrates/engine"
|
||||
|
||||
func init() {
|
||||
c := &CmdDebitBalance{
|
||||
name: "balance_debit",
|
||||
rpcMethod: "Responder.Debit",
|
||||
rpcParams: &engine.CallDescriptor{Direction: "*out"},
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdDebitBalance struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
clientArgs []string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) RpcResult() interface{} {
|
||||
return &engine.CallCost{}
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) ClientArgs() []string {
|
||||
return self.clientArgs
|
||||
}
|
||||
61
console/balance_set.go
Normal file
61
console/balance_set.go
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
c := &CmdAddBalance{
|
||||
name: "balance_set",
|
||||
rpcMethod: "ApierV1.AddBalance",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdAddBalance struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrAddBalance
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrAddBalance{BalanceType: engine.CREDIT, Overwrite: false}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdAddBalance) RpcResult() interface{} {
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -18,40 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
commands["get_cache_age"] = &CmdGetCacheAge{}
|
||||
c := &CmdGetCacheAge{
|
||||
name: "cache_age",
|
||||
rpcMethod: "ApierV1.GetCachedItemAge",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetCacheAge struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams string
|
||||
rpcResult *utils.CachedItemAge
|
||||
rpcParams *StringWrapper
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetCacheAge) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_cache_age <item_id>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetCacheAge) defaults() error {
|
||||
self.rpcMethod = "ApierV1.GetCachedItemAge"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheAge) FromArgs(args []string) error {
|
||||
if len(args) != 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
self.defaults()
|
||||
self.rpcParams = args[2]
|
||||
return nil
|
||||
func (self *CmdGetCacheAge) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheAge) RpcMethod() string {
|
||||
@@ -59,9 +46,12 @@ func (self *CmdGetCacheAge) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheAge) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &StringWrapper{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheAge) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
return &utils.CachedItemAge{}
|
||||
}
|
||||
@@ -18,37 +18,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
commands["reload_cache"] = &CmdReloadCache{}
|
||||
c := &CmdReloadCache{
|
||||
name: "cache_reload",
|
||||
rpcMethod: "ApierV1.ReloadCache",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdReloadCache struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.ApiReloadCache
|
||||
rpcResult string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdReloadCache) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] reload_cache")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdReloadCache) defaults() error {
|
||||
self.rpcMethod = "ApierV1.ReloadCache"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdReloadCache) FromArgs(args []string) error {
|
||||
self.defaults()
|
||||
return nil
|
||||
func (self *CmdReloadCache) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdReloadCache) RpcMethod() string {
|
||||
@@ -56,9 +47,13 @@ func (self *CmdReloadCache) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdReloadCache) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.ApiReloadCache{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdReloadCache) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -18,36 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
commands["get_cache_stats"] = &CmdGetCacheStats{}
|
||||
c := &CmdGetCacheStats{
|
||||
name: "cache_stats",
|
||||
rpcMethod: "ApierV1.GetCacheStats",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetCacheStats struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.AttrCacheStats
|
||||
rpcResult utils.CacheStats
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetCacheStats) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_cache_stats")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetCacheStats) defaults() error {
|
||||
self.rpcMethod = "ApierV1.GetCacheStats"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheStats) FromArgs(args []string) error {
|
||||
self.defaults()
|
||||
return nil
|
||||
func (self *CmdGetCacheStats) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheStats) RpcMethod() string {
|
||||
@@ -55,9 +46,12 @@ func (self *CmdGetCacheStats) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheStats) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.AttrCacheStats{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetCacheStats) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
return &utils.CacheStats{}
|
||||
}
|
||||
62
console/callcost.go
Normal file
62
console/callcost.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
c := &CmdGetCostDetails{
|
||||
name: "cost_details",
|
||||
rpcMethod: "ApierV1.GetCallCostLog",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetCostDetails struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrGetCallCost
|
||||
rpcResult string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdGetCostDetails) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetCostDetails) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetCostDetails) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrGetCallCost{RunId: utils.DEFAULT_RUNID}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetCostDetails) RpcResult() interface{} {
|
||||
return &engine.CallCost{}
|
||||
}
|
||||
57
console/cdrs_export.go
Normal file
57
console/cdrs_export.go
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
c := &CmdExportCdrs{
|
||||
name: "cdrs_export",
|
||||
rpcMethod: "ApierV1.ExportCdrsToFile",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdExportCdrs struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.AttrExpFileCdrs
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.AttrExpFileCdrs{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) RpcResult() interface{} {
|
||||
return &utils.ExportedFileCdrs{}
|
||||
}
|
||||
@@ -18,44 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
commands["rem_cdrs"] = &CmdRemCdrs{}
|
||||
c := &CmdRemCdrs{
|
||||
name: "cdrs_rem",
|
||||
rpcMethod: "ApierV1.RemCdrs",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdRemCdrs struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.AttrRemCdrs
|
||||
rpcResult string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdRemCdrs) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] rem_cdrs <cgrid> [<cdrid> [<cdrid>...]]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdRemCdrs) defaults() error {
|
||||
self.rpcMethod = "ApierV1.RemCdrs"
|
||||
self.rpcParams = &utils.AttrRemCdrs{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdRemCdrs) FromArgs(args []string) error {
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.CgrIds = args[2:]
|
||||
return nil
|
||||
func (self *CmdRemCdrs) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdRemCdrs) RpcMethod() string {
|
||||
@@ -63,9 +46,13 @@ func (self *CmdRemCdrs) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdRemCdrs) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.AttrRemCdrs{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdRemCdrs) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
57
console/cdrstats_metrics.go
Normal file
57
console/cdrstats_metrics.go
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/apier"
|
||||
|
||||
func init() {
|
||||
c := &CmdCdrStatsMetrics{
|
||||
name: "cdrstats_metrics",
|
||||
rpcMethod: "CDRStatsV1.GetMetrics",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdCdrStatsMetrics struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrGetMetrics
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsMetrics) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsMetrics) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsMetrics) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrGetMetrics{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsMetrics) RpcResult() interface{} {
|
||||
return &map[string]float64{}
|
||||
}
|
||||
59
console/cdrstats_queueids.go
Normal file
59
console/cdrstats_queueids.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
func init() {
|
||||
c := &CmdCdrStatsQueueIds{
|
||||
name: "cdrstats_queueids",
|
||||
rpcMethod: "CDRStatsV1.GetQueueIds",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
type CmdCdrStatsQueueIds struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *StringWrapper
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsQueueIds) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsQueueIds) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsQueueIds) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &StringWrapper{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsQueueIds) RpcResult() interface{} {
|
||||
var s []string
|
||||
return &s
|
||||
}
|
||||
|
||||
func (self *CmdCdrStatsQueueIds) ClientArgs() (args []string) {
|
||||
return
|
||||
}
|
||||
60
console/cdrstats_reload.go
Normal file
60
console/cdrstats_reload.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
c := &CmdCdrReloadQueues{
|
||||
name: "cdrstats_reload",
|
||||
rpcMethod: "CDRStatsV1.ReloadQueues",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdCdrReloadQueues struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.AttrCDRStatsReloadQueues
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdCdrReloadQueues) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdCdrReloadQueues) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdCdrReloadQueues) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.AttrCDRStatsReloadQueues{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdCdrReloadQueues) RpcResult() interface{} {
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
58
console/cdrstats_reset.go
Normal file
58
console/cdrstats_reset.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
c := &CmdCdrResetQueues{
|
||||
name: "cdrstats_reset",
|
||||
rpcMethod: "CDRStatsV1.ResetQueues",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdCdrResetQueues struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.AttrCDRStatsReloadQueues
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdCdrResetQueues) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdCdrResetQueues) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdCdrResetQueues) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.AttrCDRStatsReloadQueues{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdCdrResetQueues) RpcResult() interface{} {
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -5,6 +5,7 @@ Copyright (C) 2013 ITsysCOM
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
@@ -19,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package console
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
@@ -30,29 +30,53 @@ var (
|
||||
|
||||
// Console Command interface
|
||||
type Commander interface {
|
||||
FromArgs(args []string) error // Load data from os arguments or flag.Args()
|
||||
Usage(string) string // usage message
|
||||
RpcMethod() string // Method which should be called remotely
|
||||
RpcParams() interface{} // Parameters to send out on rpc
|
||||
RpcResult() interface{} // Only requirement is to have a String method to print on console
|
||||
defaults() error // set defaults wherever necessary
|
||||
FromArgs(args string, verbose bool) error // Load data from os arguments or flag.Args()
|
||||
Usage() string // usage message
|
||||
RpcMethod() string // Method which should be called remotely
|
||||
RpcParams() interface{} // Parameters to send out on rpc
|
||||
RpcResult() interface{} // Only requirement is to have a String method to print on console
|
||||
ClientArgs() []string // for autocompletion
|
||||
Name() string
|
||||
LocalExecute() string
|
||||
}
|
||||
|
||||
func GetCommands() map[string]Commander {
|
||||
return commands
|
||||
}
|
||||
|
||||
func getAvailabelCommandsErr() error {
|
||||
var keys []string
|
||||
for key, _ := range commands {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return fmt.Errorf("\n\tAvailable commands <%s>\n", strings.Join(keys, "|"))
|
||||
}
|
||||
|
||||
// Process args and return right command Value or error
|
||||
func GetCommandValue(args []string) (Commander, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, errors.New("\n\tUsage: cgr-console [cfg_opts...{-h}] <command>\n")
|
||||
func GetCommandValue(command string, verbose bool) (Commander, error) {
|
||||
if len(command) == 0 {
|
||||
return nil, getAvailabelCommandsErr()
|
||||
}
|
||||
cmdVal, exists := commands[args[1]]
|
||||
firstSpace := strings.Index(command, " ")
|
||||
var cmdName string
|
||||
var cmdArgs string
|
||||
if firstSpace <= 0 {
|
||||
cmdName = command[:len(command)]
|
||||
cmdArgs = ""
|
||||
} else {
|
||||
cmdName = command[:firstSpace]
|
||||
cmdArgs = command[firstSpace+1:]
|
||||
}
|
||||
cmdVal, exists := commands[cmdName]
|
||||
if !exists {
|
||||
var keys []string
|
||||
for key, _ := range commands {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return nil, fmt.Errorf("\n\tUsage: cgr-console [cfg_opts...{-h}] <%s>\n", strings.Join(keys, "|"))
|
||||
return nil, getAvailabelCommandsErr()
|
||||
}
|
||||
if err := cmdVal.FromArgs(args); err != nil {
|
||||
if err := cmdVal.FromArgs(cmdArgs, verbose); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmdVal, nil
|
||||
}
|
||||
|
||||
type StringWrapper struct {
|
||||
Item string
|
||||
}
|
||||
|
||||
100
console/command_executer.go
Normal file
100
console/command_executer.go
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
lineR = regexp.MustCompile(`(\w+)\s*=\s*(\[.+?\]|.+?)(?:\s+|$)`)
|
||||
jsonR = regexp.MustCompile(`"(\w+)":(\[.+?\]|.+?)[,|}]`)
|
||||
)
|
||||
|
||||
// Commander implementation
|
||||
type CommandExecuter struct {
|
||||
command Commander
|
||||
}
|
||||
|
||||
func (ce *CommandExecuter) Usage() string {
|
||||
jsn, _ := json.Marshal(ce.command.RpcParams())
|
||||
return fmt.Sprintf("\n\tUsage: %s %s \n", ce.command.Name(), FromJSON(jsn, ce.command.ClientArgs()))
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (ce *CommandExecuter) FromArgs(args string, verbose bool) error {
|
||||
if err := json.Unmarshal(ToJSON(args), ce.command.RpcParams()); err != nil {
|
||||
return err
|
||||
}
|
||||
if verbose {
|
||||
jsn, _ := json.Marshal(ce.command.RpcParams())
|
||||
fmt.Println(ce.command.Name(), FromJSON(jsn, ce.command.ClientArgs()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ce *CommandExecuter) ClientArgs() (args []string) {
|
||||
val := reflect.ValueOf(ce.command.RpcParams()).Elem()
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
typeField := val.Type().Field(i)
|
||||
args = append(args, typeField.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// To be overwritten by commands that do not need a rpc call
|
||||
func (ce *CommandExecuter) LocalExecute() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func ToJSON(line string) (jsn []byte) {
|
||||
if !strings.Contains(line, "=") {
|
||||
line = fmt.Sprintf("Item=\"%s\"", line)
|
||||
}
|
||||
jsn = append(jsn, '{')
|
||||
for _, group := range lineR.FindAllStringSubmatch(line, -1) {
|
||||
if len(group) == 3 {
|
||||
jsn = append(jsn, []byte(fmt.Sprintf("\"%s\":%s,", group[1], group[2]))...)
|
||||
}
|
||||
}
|
||||
jsn = bytes.TrimRight(jsn, ",")
|
||||
jsn = append(jsn, '}')
|
||||
return
|
||||
}
|
||||
|
||||
func FromJSON(jsn []byte, interestingFields []string) (line string) {
|
||||
if !bytes.Contains(jsn, []byte{':'}) {
|
||||
return fmt.Sprintf("\"%s\"", string(jsn))
|
||||
}
|
||||
for _, group := range jsonR.FindAllSubmatch(jsn, -1) {
|
||||
if len(group) == 3 {
|
||||
if utils.IsSliceMember(interestingFields, string(group[1])) {
|
||||
line += fmt.Sprintf("%s=%s ", group[1], group[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(line)
|
||||
}
|
||||
108
console/command_executer_test.go
Normal file
108
console/command_executer_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 console
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToJSON(t *testing.T) {
|
||||
jsn := ToJSON(`TimeStart="Test" Crazy = 1 Mama=true coco Test=1`)
|
||||
expected := `{"TimeStart":"Test","Crazy":1,"Mama":true,"Test":1}`
|
||||
if string(jsn) != expected {
|
||||
t.Errorf("Expected: %s got: %s", expected, jsn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSONValid(t *testing.T) {
|
||||
jsn := ToJSON(`TimeStart="Test" Crazy = 1 Mama=true coco Test=1`)
|
||||
a := make(map[string]interface{})
|
||||
if err := json.Unmarshal(jsn, &a); err != nil {
|
||||
t.Error("Error unmarshaling generated json: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSONEmpty(t *testing.T) {
|
||||
jsn := ToJSON("")
|
||||
if string(jsn) != `{"Item":""}` {
|
||||
t.Error("Error empty: ", string(jsn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSONString(t *testing.T) {
|
||||
jsn := ToJSON("1002")
|
||||
if string(jsn) != `{"Item":"1002"}` {
|
||||
t.Error("Error string: ", string(jsn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSONArrayNoSpace(t *testing.T) {
|
||||
jsn := ToJSON(`Param=["id1","id2","id3"] Another="Patram"`)
|
||||
if string(jsn) != `{"Param":["id1","id2","id3"],"Another":"Patram"}` {
|
||||
t.Error("Error string: ", string(jsn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSONArraySpace(t *testing.T) {
|
||||
jsn := ToJSON(`Param=["id1", "id2", "id3"] Another="Patram"`)
|
||||
if string(jsn) != `{"Param":["id1", "id2", "id3"],"Another":"Patram"}` {
|
||||
t.Error("Error string: ", string(jsn))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromJSON(t *testing.T) {
|
||||
line := FromJSON([]byte(`{"TimeStart":"Test","Crazy":1,"Mama":true,"Test":1}`), []string{"TimeStart", "Crazy", "Mama", "Test"})
|
||||
expected := `TimeStart="Test" Crazy=1 Mama=true Test=1`
|
||||
if line != expected {
|
||||
t.Errorf("Expected: %s got: '%s'", expected, line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromJSONInterestingFields(t *testing.T) {
|
||||
line := FromJSON([]byte(`{"TimeStart":"Test","Crazy":1,"Mama":true,"Test":1}`), []string{"TimeStart", "Test"})
|
||||
expected := `TimeStart="Test" Test=1`
|
||||
if line != expected {
|
||||
t.Errorf("Expected: %s got: '%s'", expected, line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromJSONString(t *testing.T) {
|
||||
line := FromJSON([]byte(`1002`), []string{"string"})
|
||||
expected := `"1002"`
|
||||
if line != expected {
|
||||
t.Errorf("Expected: %s got: '%s'", expected, line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromJSONArrayNoSpace(t *testing.T) {
|
||||
line := FromJSON([]byte(`{"Param":["id1","id2","id3"], "TimeStart":"Test", "Test":1}`), []string{"Param", "TimeStart", "Test"})
|
||||
expected := `Param=["id1","id2","id3"] TimeStart="Test" Test=1`
|
||||
if line != expected {
|
||||
t.Errorf("Expected: %s got: '%s'", expected, line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromJSONArraySpace(t *testing.T) {
|
||||
line := FromJSON([]byte(`{"Param":["id1", "id2", "id3"], "TimeStart":"Test", "Test":1}`), []string{"Param", "TimeStart", "Test"})
|
||||
expected := `Param=["id1", "id2", "id3"] TimeStart="Test" Test=1`
|
||||
if line != expected {
|
||||
t.Errorf("Expected: %s got: '%s'", expected, line)
|
||||
}
|
||||
}
|
||||
63
console/cost.go
Normal file
63
console/cost.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 "github.com/cgrates/cgrates/engine"
|
||||
|
||||
func init() {
|
||||
c := &CmdGetCost{
|
||||
name: "cost",
|
||||
rpcMethod: "Responder.GetCost",
|
||||
clientArgs: []string{"Direction", "Category", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetCost struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
clientArgs []string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) RpcResult() interface{} {
|
||||
return &engine.CallCost{}
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) ClientArgs() []string {
|
||||
return self.clientArgs
|
||||
}
|
||||
67
console/datacost.go
Normal file
67
console/datacost.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Real-time Charging System for Telecom & ISP environments
|
||||
Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
c := &CmdGetDataCost{
|
||||
name: "datacost",
|
||||
rpcMethod: "ApierV1.GetDataCost",
|
||||
clientArgs: []string{"Direction", "Category", "Tenant", "Account", "Subject", "StartTime", "Usage"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetDataCost struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrGetDataCost
|
||||
clientArgs []string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdGetDataCost) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetDataCost) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetDataCost) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrGetDataCost{Direction: utils.OUT}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetDataCost) RpcResult() interface{} {
|
||||
return &engine.DataCost{}
|
||||
}
|
||||
|
||||
func (self *CmdGetDataCost) ClientArgs() []string {
|
||||
return self.clientArgs
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["debit_balance"] = &CmdDebitBalance{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdDebitBalance struct {
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
rpcResult engine.CallCost
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdDebitBalance) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] debit_balance <tor> <tenant> <subject> <destination> <start_time|*now> <duration>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdDebitBalance) defaults() error {
|
||||
self.rpcMethod = "Responder.Debit"
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdDebitBalance) FromArgs(args []string) error {
|
||||
if len(args) != 8 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
var tStart time.Time
|
||||
var err error
|
||||
if args[6] == "*now" {
|
||||
tStart = time.Now()
|
||||
} else {
|
||||
tStart, err = utils.ParseDate(args[6])
|
||||
if err != nil {
|
||||
fmt.Println("\n*start_time* should have one of the formats:")
|
||||
fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC")
|
||||
fmt.Println("\tunix time\teg: 1383823746")
|
||||
fmt.Println("\t*now\t\tmetafunction transformed into localtime at query time")
|
||||
fmt.Println("\t+dur\t\tduration to be added to localtime (valid suffixes: ns, us/µs, ms, s, m, h)\n")
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
}
|
||||
callDur, err := utils.ParseDurationWithSecs(args[7])
|
||||
if err != nil {
|
||||
fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n")
|
||||
}
|
||||
self.rpcParams.TOR = args[2]
|
||||
self.rpcParams.Tenant = args[3]
|
||||
self.rpcParams.Subject = args[4]
|
||||
self.rpcParams.Destination = args[5]
|
||||
self.rpcParams.TimeStart = tStart
|
||||
self.rpcParams.CallDuration = callDur
|
||||
self.rpcParams.TimeEnd = tStart.Add(callDur)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdDebitBalance) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -18,42 +18,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
)
|
||||
import "github.com/cgrates/cgrates/engine"
|
||||
|
||||
func init() {
|
||||
commands["get_destination"] = &CmdGetDestination{}
|
||||
c := &CmdGetDestination{
|
||||
name: "destination",
|
||||
rpcMethod: "ApierV1.GetDestination",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetDestination struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams string
|
||||
rpcResult *engine.Destination
|
||||
rpcParams *StringWrapper
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetDestination) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_destination <id>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetDestination) defaults() error {
|
||||
self.rpcMethod = "Apier.GetDestination"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdGetDestination) FromArgs(args []string) error {
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams = args[2]
|
||||
return nil
|
||||
func (self *CmdGetDestination) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetDestination) RpcMethod() string {
|
||||
@@ -61,10 +46,12 @@ func (self *CmdGetDestination) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdGetDestination) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &StringWrapper{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetDestination) RpcResult() interface{} {
|
||||
self.rpcResult = new(engine.Destination)
|
||||
return self.rpcResult
|
||||
return &engine.Destination{}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["export_cdrs"] = &CmdExportCdrs{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdExportCdrs struct {
|
||||
rpcMethod string
|
||||
rpcParams *utils.AttrExpFileCdrs
|
||||
rpcResult utils.ExportedFileCdrs
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdExportCdrs) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] export_cdrs <dry_run|csv> [<start_time|*one_month> [<stop_time> ]]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdExportCdrs) defaults() error {
|
||||
self.rpcMethod = "ApierV1.ExportCdrsToFile"
|
||||
self.rpcParams = &utils.AttrExpFileCdrs{CdrFormat: "csv"}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) FromArgs(args []string) error {
|
||||
self.defaults()
|
||||
var timeStart, timeEnd string
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
if !utils.IsSliceMember(utils.CdreCdrFormats, args[2]) {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
self.rpcParams.CdrFormat = args[2]
|
||||
switch len(args) {
|
||||
case 4:
|
||||
timeStart = args[3]
|
||||
|
||||
case 5:
|
||||
timeStart = args[3]
|
||||
timeEnd = args[4]
|
||||
case 6:
|
||||
timeStart = args[3]
|
||||
timeEnd = args[4]
|
||||
}
|
||||
if timeStart == "*one_month" {
|
||||
now := time.Now()
|
||||
self.rpcParams.TimeStart = now.AddDate(0, -1, 0).String()
|
||||
self.rpcParams.TimeEnd = now.String()
|
||||
} else {
|
||||
self.rpcParams.TimeStart = timeStart
|
||||
self.rpcParams.TimeEnd = timeEnd
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdExportCdrs) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cgrates/cgrates/apier"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["get_callcost"] = &CmdGetCallCost{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetCallCost struct {
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrGetCallCost
|
||||
rpcResult *engine.CallCost
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetCallCost) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_callcost <cgrid> [<runid>]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetCallCost) defaults() error {
|
||||
self.rpcMethod = "ApierV1.GetCallCostLog"
|
||||
self.rpcParams = &apier.AttrGetCallCost{RunId: utils.DEFAULT_RUNID}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdGetCallCost) FromArgs(args []string) error {
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams.CgrId = args[2]
|
||||
if len(args) == 4 {
|
||||
self.rpcParams.RunId = args[3]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdGetCallCost) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetCallCost) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetCallCost) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["get_cost"] = &CmdGetCost{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetCost struct {
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
rpcResult engine.CallCost
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetCost) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_cost <tor> <tenant> <subject> <destination> <start_time|*now> <duration>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetCost) defaults() error {
|
||||
self.rpcMethod = "Responder.GetCost"
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdGetCost) FromArgs(args []string) error {
|
||||
if len(args) != 8 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
var tStart time.Time
|
||||
var err error
|
||||
if args[6] == "*now" {
|
||||
tStart = time.Now()
|
||||
} else {
|
||||
tStart, err = utils.ParseDate(args[6])
|
||||
if err != nil {
|
||||
fmt.Println("\n*start_time* should have one of the formats:")
|
||||
fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC")
|
||||
fmt.Println("\tunix time\teg: 1383823746")
|
||||
fmt.Println("\t*now\t\tmetafunction transformed into localtime at query time")
|
||||
fmt.Println("\t+dur\t\tduration to be added to localtime (valid suffixes: ns, us/µs, ms, s, m, h)\n")
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
}
|
||||
callDur, err := utils.ParseDurationWithSecs(args[7])
|
||||
if err != nil {
|
||||
fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n")
|
||||
}
|
||||
self.rpcParams.TOR = args[2]
|
||||
self.rpcParams.Tenant = args[3]
|
||||
self.rpcParams.Subject = args[4]
|
||||
self.rpcParams.Destination = args[5]
|
||||
self.rpcParams.TimeStart = tStart
|
||||
self.rpcParams.CallDuration = callDur
|
||||
self.rpcParams.TimeEnd = tStart.Add(callDur)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetCost) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/engine"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["get_maxduration"] = &CmdGetMaxDuration{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetMaxDuration struct {
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
rpcResult *float64
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdGetMaxDuration) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] get_maxduration <tor> <tenant> <subject> <destination> <start_time|*now> [<target_duration>]")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdGetMaxDuration) defaults() error {
|
||||
self.rpcMethod = "Responder.GetMaxSessionTime"
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdGetMaxDuration) FromArgs(args []string) error {
|
||||
if len(args) < 7 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
var tStart time.Time
|
||||
var err error
|
||||
if args[6] == "*now" {
|
||||
tStart = time.Now()
|
||||
} else {
|
||||
tStart, err = utils.ParseDate(args[6])
|
||||
if err != nil {
|
||||
fmt.Println("\n*start_time* should have one of the formats:")
|
||||
fmt.Println("\ttime.RFC3339\teg:2013-08-07T17:30:00Z in UTC")
|
||||
fmt.Println("\tunix time\teg: 1383823746")
|
||||
fmt.Println("\t*now\t\tmetafunction transformed into localtime at query time")
|
||||
fmt.Println("\t+dur\t\tduration to be added to localtime (valid suffixes: ns, us/µs, ms, s, m, h)\n")
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
}
|
||||
var callDur time.Duration
|
||||
if len(args) == 8 {
|
||||
callDur, err = utils.ParseDurationWithSecs(args[7])
|
||||
if err != nil {
|
||||
fmt.Println("\n\tExample durations: 60s for 60 seconds, 25m for 25minutes, 1m25s for one minute and 25 seconds\n")
|
||||
}
|
||||
} else { // Enforce call duration to a predefined 7200s
|
||||
callDur = time.Duration(7200) * time.Second
|
||||
}
|
||||
self.rpcParams.TOR = args[2]
|
||||
self.rpcParams.Tenant = args[3]
|
||||
self.rpcParams.Subject = args[4]
|
||||
self.rpcParams.Destination = args[5]
|
||||
self.rpcParams.TimeStart = tStart
|
||||
self.rpcParams.CallDuration = callDur
|
||||
self.rpcParams.TimeEnd = tStart.Add(callDur)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
64
console/maxduration.go
Normal file
64
console/maxduration.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 "github.com/cgrates/cgrates/engine"
|
||||
|
||||
func init() {
|
||||
c := &CmdGetMaxDuration{
|
||||
name: "maxduration",
|
||||
rpcMethod: "Responder.GetMaxSessionTime",
|
||||
clientArgs: []string{"Direction", "TOR", "Tenant", "Subject", "Account", "Destination", "TimeStart", "TimeEnd", "CallDuration", "FallbackSubject"},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdGetMaxDuration struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *engine.CallDescriptor
|
||||
clientArgs []string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &engine.CallDescriptor{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) RpcResult() interface{} {
|
||||
var f float64
|
||||
return &f
|
||||
}
|
||||
|
||||
func (self *CmdGetMaxDuration) ClientArgs() []string {
|
||||
return self.clientArgs
|
||||
}
|
||||
75
console/parse.go
Normal file
75
console/parse.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 "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
c := &CmdParse{
|
||||
name: "parse",
|
||||
rpcParams: &AttrParse{},
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
type AttrParse struct {
|
||||
Expression string
|
||||
Value string
|
||||
}
|
||||
|
||||
type CmdParse struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *AttrParse
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdParse) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdParse) RpcMethod() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *CmdParse) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &AttrParse{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdParse) RpcResult() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdParse) LocalExecute() string {
|
||||
if self.rpcParams.Expression == "" {
|
||||
return "Empty expression error"
|
||||
}
|
||||
if self.rpcParams.Value == "" {
|
||||
return "Empty value error"
|
||||
}
|
||||
if rsrField, err := utils.NewRSRField(self.rpcParams.Expression); err == nil {
|
||||
return rsrField.ParseValue(self.rpcParams.Value)
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
59
console/ratingprofile_set.go
Normal file
59
console/ratingprofile_set.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/utils"
|
||||
|
||||
func init() {
|
||||
c := &CmdSetRatingProfile{
|
||||
name: "ratingprofile_set",
|
||||
rpcMethod: "ApierV1.SetRatingProfile",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdSetRatingProfile struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *utils.TPRatingProfile
|
||||
rpcResult string
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdSetRatingProfile) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdSetRatingProfile) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdSetRatingProfile) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &utils.TPRatingProfile{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdSetRatingProfile) RpcResult() interface{} {
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -18,36 +18,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["reload_scheduler"] = &CmdReloadScheduler{}
|
||||
c := &CmdReloadScheduler{
|
||||
name: "scheduler_reload",
|
||||
rpcMethod: "ApierV1.ReloadScheduler",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdReloadScheduler struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams string
|
||||
rpcResult string
|
||||
rpcParams *StringWrapper
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdReloadScheduler) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] reload_scheduler")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdReloadScheduler) defaults() error {
|
||||
self.rpcMethod = "ApierV1.ReloadScheduler"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdReloadScheduler) FromArgs(args []string) error {
|
||||
self.defaults()
|
||||
return nil
|
||||
func (self *CmdReloadScheduler) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdReloadScheduler) RpcMethod() string {
|
||||
@@ -55,9 +44,13 @@ func (self *CmdReloadScheduler) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdReloadScheduler) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &StringWrapper{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdReloadScheduler) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cgrates/cgrates/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["set_ratingprofile"] = &CmdSetrRatingProfile{}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdSetrRatingProfile struct {
|
||||
rpcMethod string
|
||||
rpcParams *utils.TPRatingProfile
|
||||
rpcResult string
|
||||
}
|
||||
|
||||
// name should be exec's name
|
||||
func (self *CmdSetrRatingProfile) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: cgr-console [cfg_opts...{-h}] set_ratingprofile <tpid> <loadid> <tenant> <tor> <subject>")
|
||||
}
|
||||
|
||||
// set param defaults
|
||||
func (self *CmdSetrRatingProfile) defaults() error {
|
||||
self.rpcMethod = "ApierV1.SetRatingProfile"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses command line args and builds CmdBalance value
|
||||
func (self *CmdSetrRatingProfile) FromArgs(args []string) error {
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf(self.Usage(""))
|
||||
}
|
||||
// Args look OK, set defaults before going further
|
||||
self.defaults()
|
||||
self.rpcParams = &utils.TPRatingProfile{TPid: args[2], LoadId: args[3], Tenant: args[4], TOR: args[5], Direction: "*out", Subject: args[6]}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdSetrRatingProfile) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdSetrRatingProfile) RpcParams() interface{} {
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdSetrRatingProfile) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
}
|
||||
@@ -18,32 +18,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands["status"] = &CmdStatus{}
|
||||
c := &CmdStatus{
|
||||
name: "status",
|
||||
rpcMethod: "Responder.Status",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
type CmdStatus struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams string
|
||||
rpcResult string
|
||||
rpcParams *StringWrapper
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdStatus) Usage(name string) string {
|
||||
return fmt.Sprintf("\n\tUsage: %s [cfg_opts...{-h}] status", name)
|
||||
}
|
||||
|
||||
func (self *CmdStatus) defaults() error {
|
||||
self.rpcMethod = "Responder.Status"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CmdStatus) FromArgs(args []string) error {
|
||||
self.defaults()
|
||||
return nil
|
||||
func (self *CmdStatus) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdStatus) RpcMethod() string {
|
||||
@@ -51,9 +43,17 @@ func (self *CmdStatus) RpcMethod() string {
|
||||
}
|
||||
|
||||
func (self *CmdStatus) RpcParams() interface{} {
|
||||
return &self.rpcParams
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &StringWrapper{}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdStatus) RpcResult() interface{} {
|
||||
return &self.rpcResult
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
|
||||
func (self *CmdStatus) ClientArgs() (args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
58
console/triggeredaction_add.go
Normal file
58
console/triggeredaction_add.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Rating system designed to be used in VoIP Carriers World
|
||||
Copyright (C) 2013 ITsysCOM
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import "github.com/cgrates/cgrates/apier"
|
||||
|
||||
func init() {
|
||||
c := &CmdAddTriggeredAction{
|
||||
name: "triggeredaction_add",
|
||||
rpcMethod: "ApierV1.AddTriggeredAction",
|
||||
}
|
||||
commands[c.Name()] = c
|
||||
c.CommandExecuter = &CommandExecuter{c}
|
||||
}
|
||||
|
||||
// Commander implementation
|
||||
type CmdAddTriggeredAction struct {
|
||||
name string
|
||||
rpcMethod string
|
||||
rpcParams *apier.AttrAddActionTrigger
|
||||
*CommandExecuter
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) Name() string {
|
||||
return self.name
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) RpcMethod() string {
|
||||
return self.rpcMethod
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) RpcParams() interface{} {
|
||||
if self.rpcParams == nil {
|
||||
self.rpcParams = &apier.AttrAddActionTrigger{Direction: "*out"}
|
||||
}
|
||||
return self.rpcParams
|
||||
}
|
||||
|
||||
func (self *CmdAddTriggeredAction) RpcResult() interface{} {
|
||||
var s string
|
||||
return &s
|
||||
}
|
||||
@@ -1,39 +1,39 @@
|
||||
# CGRateS Configuration file
|
||||
# Real-time Charging System for Telecom & ISP environments
|
||||
# Copyright (C) ITsysCOM GmbH
|
||||
#
|
||||
# This file contains the default configuration hardcoded into CGRateS.
|
||||
# This is what you get when you load CGRateS with an empty configuration file.
|
||||
# [global] must exist in all files, rest of the configuration is inter-changeable.
|
||||
|
||||
[global]
|
||||
# ratingdb_type = redis # Rating subsystem database: <redis>.
|
||||
# ratingdb_host = 127.0.0.1 # Rating subsystem database host address.
|
||||
# ratingdb_host = 127.0.0.1 # Rating subsystem database host address.
|
||||
# ratingdb_port = 6379 # Rating subsystem port to reach the database.
|
||||
# ratingdb_name = 10 # Rating subsystem database name to connect to.
|
||||
# ratingdb_user = # Rating subsystem username to use when connecting to database.
|
||||
# ratingdb_passwd = # Rating subsystem password to use when connecting to database.
|
||||
# ratingdb_user = # Rating subsystem username to use when connecting to database.
|
||||
# ratingdb_passwd = # Rating subsystem password to use when connecting to database.
|
||||
# accountdb_type = redis # Accounting subsystem database: <redis>.
|
||||
# accountdb_host = 127.0.0.1 # Accounting subsystem database host address.
|
||||
# accountdb_host = 127.0.0.1 # Accounting subsystem database host address.
|
||||
# accountdb_port = 6379 # Accounting subsystem port to reach the database.
|
||||
# accountdb_name = 11 # Accounting subsystem database name to connect to.
|
||||
# accountdb_user = # Accounting subsystem username to use when connecting to database.
|
||||
# accountdb_user = # Accounting subsystem username to use when connecting to database.
|
||||
# accountdb_passwd = # Accounting subsystem password to use when connecting to database.
|
||||
# stordb_type = mysql # Stor database type to use: <mysql>
|
||||
# stordb_host = 127.0.0.1 # The host to connect to. Values that start with / are for UNIX domain sockets.
|
||||
# stordb_port = 3306 # The port to reach the logdb.
|
||||
# stordb_name = cgrates # The name of the log database to connect to.
|
||||
# stordb_user = cgrates # Username to use when connecting to stordb.
|
||||
# stordb_passwd = CGRateS.org # Password to use when connecting to stordb.
|
||||
# stordb_passwd = CGRateS.org # Password to use when connecting to stordb.
|
||||
# dbdata_encoding = msgpack # The encoding used to store object data in strings: <msgpack|json>
|
||||
# rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address
|
||||
# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address
|
||||
# http_listen = 127.0.0.1:2080 # HTTP listening address
|
||||
# rpc_json_listen = 127.0.0.1:2012 # RPC JSON listening address
|
||||
# rpc_gob_listen = 127.0.0.1:2013 # RPC GOB listening address
|
||||
# http_listen = 127.0.0.1:2080 # HTTP listening address
|
||||
# default_reqtype = rated # Default request type to consider when missing from requests: <""|prepaid|postpaid|pseudoprepaid|rated>.
|
||||
# default_tor = call # Default Type of Record to consider when missing from requests.
|
||||
# default_tenant = cgrates.org # Default Tenant to consider when missing from requests.
|
||||
# default_category = call # Default Type of Record to consider when missing from requests.
|
||||
# default_tenant = cgrates.org # Default Tenant to consider when missing from requests.
|
||||
# default_subject = cgrates # Default rating Subject to consider when missing from requests.
|
||||
# rounding_method = *middle # Rounding method for floats/costs: <*up|*middle|*down>
|
||||
# rounding_decimals = 4 # Number of decimals to round float/costs at
|
||||
# xmlcfg_path = # Path towards additional config defined in xml file
|
||||
# rounding_decimals = 10 # System level precision for floats
|
||||
# http_skip_tls_veify = false # If enabled Http Client will accept any TLS certificate
|
||||
# xmlcfg_path = # Path towards additional config defined in xml file
|
||||
|
||||
[balancer]
|
||||
# enabled = false # Start Balancer service: <true|false>.
|
||||
@@ -49,87 +49,126 @@
|
||||
# enabled = false # Start the CDR Server service: <true|false>.
|
||||
# extra_fields = # Extra fields to store in CDRs for non-generic CDRs
|
||||
# mediator = # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
# cdrstats = # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
|
||||
# store_disable = false # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
|
||||
|
||||
[cdre]
|
||||
# cdr_format = csv # Exported CDRs format <csv>
|
||||
# export_dir = /var/log/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
|
||||
# export_template = cgrid,mediation_runid,accid,cdrhost,reqtype,direction,tenant,tor,account,subject,destination,setup_time,answer_time,duration,cost
|
||||
# Exported fields template <""|fld1,fld2|*xml:instance_name>
|
||||
|
||||
# cdr_format = csv # Exported CDRs format <csv>
|
||||
# data_usage_multiply_factor = 0.0 # Multiply data usage before export (eg: convert from KBytes to Bytes)
|
||||
# cost_multiply_factor = 0.0 # Multiply cost before export (0.0 to disable), eg: add VAT
|
||||
# cost_rounding_decimals = -1 # Rounding decimals for Cost values. -1 to disable rounding
|
||||
# cost_shift_digits = 0 # Shift digits in the cost on export (eg: convert from EUR to cents)
|
||||
# mask_destination_id = # Destination id containing called addresses to be masked on export
|
||||
# mask_length = 0 # Length of the destination suffix to be masked
|
||||
# export_dir = /var/log/cgrates/cdre # Path where the exported CDRs will be placed
|
||||
# export_template = cgrid,mediation_runid,tor,accid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,usage,cost
|
||||
# Exported fields template <""|fld1,fld2|*xml:instance_name>
|
||||
[cdrc]
|
||||
# enabled = false # Enable CDR client functionality
|
||||
# cdrs = internal # Address where to reach CDR server. <internal|127.0.0.1:2080>
|
||||
# cdrs_method = http_cgr # Mechanism to use when posting CDRs on server <http_cgr>
|
||||
# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify
|
||||
# cdr_type = csv # CDR file format <csv|freeswitch_csv>.
|
||||
# cdr_in_dir = /var/log/cgrates/cdr/cdrc/in # Absolute path towards the directory where the CDRs are stored.
|
||||
# cdr_out_dir = /var/log/cgrates/cdr/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
|
||||
# cdr_source_id = freeswitch_csv # Free form field, tag identifying the source of the CDRs within CGRS database.
|
||||
# accid_field = 0 # Accounting id field identifier. Use index number in case of .csv cdrs.
|
||||
# reqtype_field = 1 # Request type field identifier. Use index number in case of .csv cdrs.
|
||||
# direction_field = 2 # Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
# tenant_field = 3 # Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
# tor_field = 4 # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
# account_field = 5 # Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
# subject_field = 6 # Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
# destination_field = 7 # Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
# setup_time_field = 8 # Setup time field identifier. Use index numbers in case of .csv cdrs.
|
||||
# answer_time_field = 9 # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
# duration_field = 10 # Duration field identifier. Use index numbers in case of .csv cdrs.
|
||||
# extra_fields = # Extra fields identifiers. For .csv, format: <label_extrafield_1>:<index_extrafield_1>[...,<label_extrafield_n>:<index_extrafield_n>]
|
||||
# enabled = false # Enable CDR client functionality
|
||||
# cdrs = internal # Address where to reach CDR server. <internal|127.0.0.1:2080>
|
||||
# run_delay = 0 # Sleep interval in seconds between consecutive runs, 0 to use automation via inotify
|
||||
# cdr_type = csv # CDR file format <csv|freeswitch_csv>.
|
||||
# csv_separator = , # Separator used in case of csv files. One character only supported and needs to be right after equal sign
|
||||
# cdr_in_dir = /var/log/cgrates/cdrc/in # Absolute path towards the directory where the CDRs are stored.
|
||||
# cdr_out_dir = /var/log/cgrates/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
|
||||
# cdr_source_id = csv # Free form field, tag identifying the source of the CDRs within CGRS database.
|
||||
# tor_field = 2 # TypeOfRecord field identifier. Use index number in case of .csv cdrs.
|
||||
# accid_field = 3 # Accounting id field identifier. Use index number in case of .csv cdrs.
|
||||
# reqtype_field = 4 # Request type field identifier. Use index number in case of .csv cdrs.
|
||||
# direction_field = 5 # Direction field identifier. Use index numbers in case of .csv cdrs.
|
||||
# tenant_field = 6 # Tenant field identifier. Use index numbers in case of .csv cdrs.
|
||||
# category_field = 7 # Type of Record field identifier. Use index numbers in case of .csv cdrs.
|
||||
# account_field = 8 # Account field identifier. Use index numbers in case of .csv cdrs.
|
||||
# subject_field = 9 # Subject field identifier. Use index numbers in case of .csv CDRs.
|
||||
# destination_field = 10 # Destination field identifier. Use index numbers in case of .csv cdrs.
|
||||
# setup_time_field = 11 # Setup time field identifier. Use index numbers in case of .csv cdrs.
|
||||
# answer_time_field = 12 # Answer time field identifier. Use index numbers in case of .csv cdrs.
|
||||
# usage_field = 13 # Usage field identifier. Use index numbers in case of .csv cdrs.
|
||||
# extra_fields = # Extra fields identifiers. For .csv, format: <label_extrafield_1>:<index_extrafield_1>[...,<label_extrafield_n>:<index_extrafield_n>]
|
||||
|
||||
[mediator]
|
||||
# enabled = false # Starts Mediator service: <true|false>.
|
||||
# reconnects = 3 # Number of reconnects to rater/cdrs before giving up.
|
||||
# rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
|
||||
# run_ids = # Identifiers of each extra mediation to run on CDRs
|
||||
# reqtype_fields = # Name of request type fields to be used during extra mediation. Use index number in case of .csv cdrs.
|
||||
# direction_fields = # Name of direction fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# tenant_fields = # Name of tenant fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# tor_fields = # Name of tor fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# account_fields = # Name of account fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# subject_fields = # Name of fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# destination_fields = # Name of destination fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# setup_time_fields = # Name of setup_time fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# answer_time_fields = # Name of answer_time fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# duration_fields = # Name of duration fields to be used during extra mediation. Use index numbers in case of .csv cdrs.
|
||||
# cdrstats = internal # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
|
||||
# store_disable = false # When true, CDRs will not longer be saved in stordb, useful for cdrstats only scenario
|
||||
|
||||
|
||||
[cdrstats]
|
||||
# enabled = false # Starts the cdrstats service: <true|false>
|
||||
# queue_length = 50 # Number of items in the stats buffer
|
||||
# time_window = 1h # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
|
||||
# metrics = ASR, ACD, ACC # Stat metric ids to build
|
||||
# setup_interval = # Filter on CDR SetupTime
|
||||
# tors = # Filter on CDR TOR fields
|
||||
# cdr_hosts= # Filter on CDR CdrHost fields
|
||||
# cdr_sources = # Filter on CDR CdrSource fields
|
||||
# req_types = # Filter on CDR ReqType fields
|
||||
# directions = # Filter on CDR Direction fields
|
||||
# tenants = # Filter on CDR Tenant fields
|
||||
# categories = # Filter on CDR Category fields
|
||||
# accounts = # Filter on CDR Account fields
|
||||
# subjects = # Filter on CDR Subject fields
|
||||
# destination_prefixes = # Filter on CDR Destination prefixes
|
||||
# usage_interval = # Filter on CDR Usage
|
||||
# mediation_run_ids = # Filter on CDR MediationRunId fields
|
||||
# rated_accounts = # Filter on CDR RatedAccount fields
|
||||
# rated_subjects = # Filter on CDR RatedSubject fields
|
||||
# cost_intervals = # Filter on CDR Cost
|
||||
|
||||
[session_manager]
|
||||
# enabled = false # Starts SessionManager service: <true|false>.
|
||||
# switch_type = freeswitch # Defines the type of switch behind: <freeswitch>.
|
||||
# rater = internal # Address where to reach the Rater.
|
||||
# rater_reconnects = 3 # Number of reconnects to rater before giving up.
|
||||
# debit_interval = 10 # Interval to perform debits on.
|
||||
# max_call_duration = 3h # Maximum call duration a prepaid call can last
|
||||
# run_ids = # Identifiers of additional sessions control.
|
||||
# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# tor_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# enabled = false # Starts SessionManager service: <true|false>
|
||||
# switch_type = freeswitch # Defines the type of switch behind: <freeswitch>
|
||||
# rater = internal # Address where to reach the Rater <""|internal|127.0.0.1:2013>
|
||||
# cdrs = # Address where to reach CDR Server, empty to disable CDR capturing <""|internal|127.0.0.1:2013>
|
||||
# reconnects = 3 # Number of reconnects to rater/cdrs before giving up.
|
||||
# debit_interval = 10 # Interval to perform debits on.
|
||||
# min_call_duration = 0s # Only authorize calls with allowed duration bigger than this
|
||||
# max_call_duration = 3h # Maximum call duration a prepaid call can last
|
||||
|
||||
[freeswitch]
|
||||
# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
|
||||
# server = 127.0.0.1:8021 # Adress where to connect to FreeSWITCH socket.
|
||||
# passwd = ClueCon # FreeSWITCH socket password.
|
||||
# reconnects = 5 # Number of attempts on connect failure.
|
||||
# min_dur_low_balance = 5s # Threshold which will trigger low balance warnings for prepaid calls (needs to be lower than debit_interval)
|
||||
# low_balance_ann_file = # File to be played when low balance is reached for prepaid calls
|
||||
# empty_balance_context = # If defined, prepaid calls will be transfered to this context on empty balance
|
||||
# empty_balance_ann_file = # File to be played before disconnecting prepaid calls on empty balance (applies only if no context defined)
|
||||
# cdr_extra_fields = # Extra fields to store in CDRs in case of processing them
|
||||
|
||||
[opensips]
|
||||
# listen_udp = 127.0.0.1:2020 # Address where to listen for datagram events coming from OpenSIPS
|
||||
# mi_addr = 127.0.0.1:8020 # Adress where to reach OpenSIPS mi_datagram module
|
||||
# events_subscribe_interval = 60s # Automatic events subscription to OpenSIPS, 0 to disable it
|
||||
# reconnects = 3 # Number of attempts on connect failure.
|
||||
|
||||
[derived_charging]
|
||||
# run_ids = # Identifiers of additional sessions control.
|
||||
# run_filters = # List of cdr field filters for each run.
|
||||
# reqtype_fields = # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# direction_fields = # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# tenant_fields = # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# category_fields = # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# account_fields = # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# subject_fields = # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# destination_fields = # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# usage_fields = # Name of usage fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
|
||||
|
||||
[history_server]
|
||||
# enabled = false # Starts History service: <true|false>.
|
||||
# enabled = false # Starts History service: <true|false>.
|
||||
# history_dir = /var/log/cgrates/history # Location on disk where to store history files.
|
||||
# save_interval = 1s # Interval to save changed cache into .git archive
|
||||
# save_interval = 1s # Interval to save changed cache into .git archive
|
||||
|
||||
[history_agent]
|
||||
# enabled = false # Starts History as a client: <true|false>.
|
||||
# server = internal # Address where to reach the master history server: <internal|x.y.z.y:1234>
|
||||
|
||||
[mailer]
|
||||
# server = localhost # The server to use when sending emails out
|
||||
# auth_user = cgrates # Authenticate to email server using this user
|
||||
# auth_passwd = CGRateS.org # Authenticate to email server with this password
|
||||
# server = localhost # The server to use when sending emails out
|
||||
# auth_user = cgrates # Authenticate to email server using this user
|
||||
# auth_passwd = CGRateS.org # Authenticate to email server with this password
|
||||
# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out
|
||||
|
||||
|
||||
@@ -18,18 +18,15 @@ mediator = internal # Address where to reach the Mediator. Empty for disabli
|
||||
export_dir = /tmp/cgrates/cdr/cdre/csv # Path where the exported CDRs will be placed
|
||||
|
||||
[cdrc]
|
||||
enabled = true
|
||||
cdrs = 127.0.0.1:2080
|
||||
cdr_in_dir = /tmp/cgrates/cdr/cdrc/in # Absolute path towards the directory where the CDRs are stored.
|
||||
cdr_out_dir =/tmp/cgrates/cdr/cdrc/out # Absolute path towards the directory where processed CDRs will be moved.
|
||||
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
rater = 127.0.0.1:2012 # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
|
||||
[history_server]
|
||||
enabled = true # Starts History service: <true|false>.
|
||||
history_dir = /tmp/cgrates/history # Location on disk where to store history files.
|
||||
|
||||
[history_agent]
|
||||
enabled = true # Starts History as a client: <true|false>.
|
||||
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
cdrstats=
|
||||
|
||||
|
||||
|
||||
|
||||
22
data/conf/samples/cdrstatsv1_local_test.cfg
Normal file
22
data/conf/samples/cdrstatsv1_local_test.cfg
Normal file
@@ -0,0 +1,22 @@
|
||||
# Real-time Charging System for Telecom & ISP environments
|
||||
# Copyright (C) ITsysCOM GmbH
|
||||
#
|
||||
# This file contains the default configuration hardcoded into CGRateS.
|
||||
# This is what you get when you load CGRateS with an empty configuration file.
|
||||
|
||||
[rater]
|
||||
enabled = true
|
||||
|
||||
[cdrs]
|
||||
enabled = true
|
||||
mediator = internal
|
||||
store_disable = true
|
||||
|
||||
[mediator]
|
||||
enabled = true
|
||||
store_disable = true
|
||||
|
||||
[cdrstats]
|
||||
enabled = true
|
||||
queue_length = 5 # Maximum number of items in the stats buffer
|
||||
time_window = 0 # Queue is not affected by the SetupTime
|
||||
@@ -1,20 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdre" type="fixed_width" id="CDREFW-A">
|
||||
<header>
|
||||
<fields>
|
||||
<field name="Filler1" type="filler" width="4"/>
|
||||
</fields>
|
||||
</header>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="call"/>
|
||||
</fields>
|
||||
</content>
|
||||
<trailer>
|
||||
<fields>
|
||||
<field name="Filler1" type="filler" width="3"/>
|
||||
</fields>
|
||||
</trailer>
|
||||
<cdr_format>fwv</cdr_format>
|
||||
<data_usage_multiply_factor>0.0</data_usage_multiply_factor>
|
||||
<cost_multiply_factor>0.0</cost_multiply_factor>
|
||||
<cost_shift_digits>0</cost_shift_digits>
|
||||
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
|
||||
<mask_length>0</mask_length>
|
||||
<export_dir>/var/log/cgrates/cdre</export_dir>
|
||||
<export_template>
|
||||
<header>
|
||||
<fields>
|
||||
<field name="Filler1" type="filler" width="4" />
|
||||
</fields>
|
||||
</header>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="TypeOfRecord" type="constant" value="call" />
|
||||
</fields>
|
||||
</content>
|
||||
<trailer>
|
||||
<fields>
|
||||
<field name="Filler1" type="filler" width="3" />
|
||||
</fields>
|
||||
</trailer>
|
||||
</export_template>
|
||||
</configuration>
|
||||
</document>
|
||||
|
||||
32
data/conf/samples/derived_charging1.cfg
Normal file
32
data/conf/samples/derived_charging1.cfg
Normal file
@@ -0,0 +1,32 @@
|
||||
# CGRateS Configuration file
|
||||
#
|
||||
# Used in mediator_local_test
|
||||
# Starts rater, cdrs and mediator connecting over internal channel
|
||||
|
||||
[rater]
|
||||
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
|
||||
|
||||
[cdrs]
|
||||
enabled = true # Start the CDR Server service: <true|false>.
|
||||
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
|
||||
[cdre]
|
||||
export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will be placed
|
||||
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
|
||||
[derived_charging]
|
||||
run_ids = run2 # Identifiers of additional sessions control.
|
||||
reqtype_fields = *default # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
direction_fields = *default # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tenant_fields = *default # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tor_fields = *default # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
account_fields = ^dc2 # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
subject_fields = ^dc2 # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
destination_fields = *default # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# setup_time_fields = # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# answer_time_fields = # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# duration_fields = # Name of duration fields to be used during additional sessions control <""|*default|field_name>.
|
||||
# combined_chargers = true # Combine accounts specific derived_chargers with server configured ones <true|false>.
|
||||
@@ -6,6 +6,9 @@
|
||||
[rater]
|
||||
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
|
||||
|
||||
[scheduler]
|
||||
enabled = true # Starts Scheduler service: <true|false>.
|
||||
|
||||
[cdrs]
|
||||
enabled = true # Start the CDR Server service: <true|false>.
|
||||
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
@@ -16,5 +19,20 @@ export_dir = /tmp/cgrates/cdr/cdrexport/csv # Path where the exported CDRs will
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
cdrstats =
|
||||
|
||||
[derived_charging]
|
||||
run_ids = run2 # Identifiers of additional sessions control.
|
||||
reqtype_fields = *default # Name of request type fields to be used during additional sessions control <""|*default|field_name>.
|
||||
direction_fields = *default # Name of direction fields to be used during additional sessions control <""|*default|field_name>.
|
||||
tenant_fields = *default # Name of tenant fields to be used during additional sessions control <""|*default|field_name>.
|
||||
category_fields = *default # Name of tor fields to be used during additional sessions control <""|*default|field_name>.
|
||||
account_fields = ^dc2 # Name of account fields to be used during additional sessions control <""|*default|field_name>.
|
||||
subject_fields = ^dc2 # Name of fields to be used during additional sessions control <""|*default|field_name>.
|
||||
destination_fields = *default # Name of destination fields to be used during additional sessions control <""|*default|field_name>.
|
||||
setup_time_fields = *default # Name of setup_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
answer_time_fields = *default # Name of answer_time fields to be used during additional sessions control <""|*default|field_name>.
|
||||
usage_fields = *default # Name of usage fields to be used during additional sessions control <""|*default|field_name>.
|
||||
|
||||
|
||||
|
||||
|
||||
28
data/conf/samples/multiplecdrc_fwexport.cfg
Normal file
28
data/conf/samples/multiplecdrc_fwexport.cfg
Normal file
@@ -0,0 +1,28 @@
|
||||
# Real-time Charging System for Telecom & ISP environments
|
||||
# Copyright (C) 2012-2014 ITsysCOM GmbH
|
||||
|
||||
[global]
|
||||
xmlcfg_path = /usr/share/cgrates/conf/samples/multiplecdrc_fwexport.xml
|
||||
|
||||
[rater]
|
||||
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
|
||||
|
||||
[scheduler]
|
||||
enabled = true # Starts Scheduler service: <true|false>.
|
||||
|
||||
[cdrs]
|
||||
enabled = true # Start the CDR Server service: <true|false>.
|
||||
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
|
||||
[cdre]
|
||||
export_dir = /tmp/cgrates/cdr/cdre/csv # Path where the exported CDRs will be placed
|
||||
export_template = *xml:CDRE-FW1
|
||||
|
||||
[cdrc]
|
||||
enabled = true
|
||||
cdr_in_dir = /tmp/cgrates/cdrc1/in # Absolute path towards the directory where the CDRs are stored.
|
||||
cdr_out_dir =/tmp/cgrates/cdrc1/out # Absolute path towards the directory where processed CDRs will be moved.
|
||||
cdr_source_id = csv1 # Free form field, tag identifying the source of the CDRs within CGRS database.
|
||||
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
111
data/conf/samples/multiplecdrc_fwexport.xml
Normal file
111
data/conf/samples/multiplecdrc_fwexport.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="cgrates/xml">
|
||||
<configuration section="cdrc" type="csv" id="CDRC-CSV2">
|
||||
<enabled>true</enabled>
|
||||
<cdrs_address>internal</cdrs_address>
|
||||
<cdr_type>csv</cdr_type>
|
||||
<csv_separator>,</csv_separator>
|
||||
<run_delay>0</run_delay>
|
||||
<cdr_in_dir>/tmp/cgrates/cdrc2/in</cdr_in_dir>
|
||||
<cdr_out_dir>/tmp/cgrates/cdrc2/out</cdr_out_dir>
|
||||
<cdr_source_id>csv2</cdr_source_id>
|
||||
<fields>
|
||||
<field id="tor" filter="~7:s/^(voice|data|sms)$/*$1/" />
|
||||
<field id="accid" filter="0" />
|
||||
<field id="reqtype" filter="^rated" />
|
||||
<field id="direction" filter="^*out" />
|
||||
<field id="tenant" filter="^cgrates.org" />
|
||||
<field id="category" filter="~7:s/^voice$/call/" />
|
||||
<field id="account" filter="3" />
|
||||
<field id="subject" filter="3" />
|
||||
<field id="destination" filter="~5:s/^0([1-9]\d+)$/+49$1/" />
|
||||
<field id="setup_time" filter="1" />
|
||||
<field id="answer_time" filter="1" />
|
||||
<field id="usage" filter="~9:s/^(\d+)$/${1}s/" />
|
||||
</fields>
|
||||
</configuration>
|
||||
<configuration section="cdrc" type="csv" id="CDRC-CSV3">
|
||||
<enabled>true</enabled>
|
||||
<cdrs_address>internal</cdrs_address>
|
||||
<cdr_type>csv</cdr_type>
|
||||
<csv_separator>;</csv_separator>
|
||||
<run_delay>0</run_delay>
|
||||
<cdr_in_dir>/tmp/cgrates/cdrc3/in</cdr_in_dir>
|
||||
<cdr_out_dir>/tmp/cgrates/cdrc3/out</cdr_out_dir>
|
||||
<cdr_source_id>csv3</cdr_source_id>
|
||||
<fields>
|
||||
<field id="tor" filter="^*voice" />
|
||||
<field id="accid" filter="~3:s/^(\d{2})\.(\d{2})\.(\d{4})\s{2}(\d{2}):(\d{2}):(\d{2})$/$1$2$3$4$5$6/" />
|
||||
<field id="reqtype" filter="^rated" />
|
||||
<field id="direction" filter="^*out" />
|
||||
<field id="tenant" filter="^cgrates.org" />
|
||||
<field id="category" filter="^call" />
|
||||
<field id="account" filter="~0:s/^([1-9]\d+)$/+$1/" />
|
||||
<field id="subject" filter="~0:s/^([1-9]\d+)$/+$1/" />
|
||||
<field id="destination" filter="~1:s/^([1-9]\d+)$/+$1/" />
|
||||
<field id="setup_time" filter="4" />
|
||||
<field id="answer_time" filter="4" />
|
||||
<field id="usage" filter="~6:s/^(\d+)$/${1}s/" />
|
||||
</fields>
|
||||
</configuration>
|
||||
<configuration section="cdre" type="fwv" id="CDRE-FW1">
|
||||
<cdr_format>fwv</cdr_format>
|
||||
<data_usage_multiply_factor>0.0</data_usage_multiply_factor>
|
||||
<cost_multiply_factor>0.0</cost_multiply_factor>
|
||||
<cost_shift_digits>0</cost_shift_digits>
|
||||
<mask_destination_id>MASKED_DESTINATIONS</mask_destination_id>
|
||||
<mask_length>0</mask_length>
|
||||
<export_dir>/var/log/cgrates/cdre</export_dir>
|
||||
<export_template>
|
||||
<header>
|
||||
<fields>
|
||||
<field name="ToR" type="constant" value="10" width="2" />
|
||||
<field name="Filler1" type="filler" width="3" />
|
||||
<field name="FileType" type="constant" value="SIP" width="3" />
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
|
||||
<field name="LastCdr" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
|
||||
<field name="FileCreationfTime" type="metatag" value="time_now" layout="020106150405" width="12" />
|
||||
<field name="FileVersion" type="constant" value="01" width="2" />
|
||||
<field name="Filler2" type="filler" width="105" />
|
||||
</fields>
|
||||
</header>
|
||||
<content>
|
||||
<fields>
|
||||
<field name="ToR" type="constant" value="20" width="2" />
|
||||
<field name="Subject" type="cdrfield" value="subject" width="12" padding="right" mandatory="true" />
|
||||
<field name="ConnectionNumber" type="constant" value="00000" width="5" />
|
||||
<field name="CallerId" type="cdrfield" value="~callerid:s/\+(\d+)/00$1/" strip="xright" width="15" padding="right" />
|
||||
<field name="Destination" type="cdrfield" value="~destination:s/^\+311400(\d+)/$1/:s/^\+311412\d\d112/112/:s/^\+31(\d+)/0$1/:s/^\+(\d+)/00$1/" strip="xright" width="24" padding="right" mandatory="true" />
|
||||
<field name="TypeOfService" type="constant" value="00" width="2" />
|
||||
<field name="ServiceId" type="constant" value="11" width="4" padding="right" />
|
||||
<field name="AnswerTime" type="cdrfield" value="answer_time" layout="020106150405" width="12" mandatory="true" />
|
||||
<field name="Usage" type="cdrfield" value="usage" layout="seconds" width="6" padding="right" mandatory="true" />
|
||||
<field name="DataCounter" type="filler" width="6" />
|
||||
<field name="VatCode" type="constant" value="1" width="1" />
|
||||
<field name="NetworkId" type="constant" value="S1" width="2" />
|
||||
<field name="DestinationSubId" type="cdrfield" value="~cost_details:s/"MatchedDestId":".+_(\w{5})"/$1/:s/(\w{6})/$1/" width="5" />
|
||||
<field name="NetworkSubtype" type="constant" value="3" width="1" padding="left" />
|
||||
<field name="CgrId" type="cdrfield" value="cgrid" strip="xleft" width="16" paddingi="right" mandatory="true" />
|
||||
<field name="FillerVolume1" type="filler" width="8" />
|
||||
<field name="FillerVolume2" type="filler" width="8" />
|
||||
<field name="DestinationSubId" type="cdrfield" value="~cost_details:s/"MatchedDestId":".+_(\w{5})"/$1/:s/(\w{6})/$1/" width="5" />
|
||||
<field name="Cost" type="cdrfield" value="cost" padding="zeroleft" width="9" />
|
||||
<field name="MaskDestination" type="metatag" value="mask_destination" width="1" />
|
||||
</fields>
|
||||
</content>
|
||||
<trailer>
|
||||
<fields>
|
||||
<field name="ToR" type="constant" value="90" width="2" />
|
||||
<field name="Filler1" type="filler" width="3" />
|
||||
<field name="FileType" type="constant" value="SIP" width="3" />
|
||||
<field name="FileSeqNr" type="metatag" value="export_id" padding="zeroleft" width="5" />
|
||||
<field name="TotalRecords" type="metatag" value="cdrs_number" padding="zeroleft" width="6" />
|
||||
<field name="TotalDuration" type="metatag" value="cdrs_duration" padding="zeroleft" width="8" />
|
||||
<field name="FirstCdrTime" type="metatag" value="first_cdr_atime" layout="020106150405" width="12" />
|
||||
<field name="LastCdrTime" type="metatag" value="last_cdr_atime" layout="020106150405" width="12" />
|
||||
<field name="Filler1" type="filler" width="93" />
|
||||
</fields>
|
||||
</trailer>
|
||||
</export_template>
|
||||
</configuration>
|
||||
</document>
|
||||
61
data/conf/samples/osips_cdrs_cdrstats.cfg
Normal file
61
data/conf/samples/osips_cdrs_cdrstats.cfg
Normal file
@@ -0,0 +1,61 @@
|
||||
# Real-time Charging System for Telecom & ISP environments
|
||||
# Copyright (C) ITsysCOM GmbH
|
||||
#
|
||||
# This file contains the default configuration hardcoded into CGRateS.
|
||||
# This is what you get when you load CGRateS with an empty configuration file.
|
||||
|
||||
[global]
|
||||
rpc_json_listen = :2012 # RPC JSON listening address
|
||||
|
||||
[rater]
|
||||
enabled = true # Enable RaterCDRSExportPath service: <true|false>.
|
||||
|
||||
[scheduler]
|
||||
enabled = true # Starts Scheduler service: <true|false>.
|
||||
|
||||
[cdrs]
|
||||
enabled = true # Start the CDR Server service: <true|false>.
|
||||
mediator = internal # Address where to reach the Mediator. Empty for disabling mediation. <""|internal>
|
||||
# cdrstats = # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
|
||||
|
||||
[mediator]
|
||||
enabled = true # Starts Mediator service: <true|false>.
|
||||
# rater = internal # Address where to reach the Rater: <internal|x.y.z.y:1234>
|
||||
# cdrstats = internal # Address where to reach the cdrstats service: <internal|x.y.z.y:1234>
|
||||
|
||||
[cdrstats]
|
||||
enabled = true # Starts the cdrstats service: <true|false>
|
||||
#queue_length = 50 # Number of items in the stats buffer
|
||||
time_window = 1h # Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow
|
||||
# metrics = ASR, ACD, ACC # Stat metric ids to build
|
||||
# setup_interval = # Filter on CDR SetupTime
|
||||
# tors = # Filter on CDR TOR fields
|
||||
# cdr_hosts= # Filter on CDR CdrHost fields
|
||||
# cdr_sources = # Filter on CDR CdrSource fields
|
||||
# req_types = # Filter on CDR ReqType fields
|
||||
# directions = # Filter on CDR Direction fields
|
||||
# tenants = # Filter on CDR Tenant fields
|
||||
# categories = # Filter on CDR Category fields
|
||||
# accounts = # Filter on CDR Account fields
|
||||
# subjects = # Filter on CDR Subject fields
|
||||
# destination_prefixes = # Filter on CDR Destination prefixes
|
||||
# usage_interval = # Filter on CDR Usage
|
||||
# mediation_run_ids = # Filter on CDR MediationRunId fields
|
||||
# rated_accounts = # Filter on CDR RatedAccount fields
|
||||
# rated_subjects = # Filter on CDR RatedSubject fields
|
||||
# cost_intervals = # Filter on CDR Cost
|
||||
|
||||
[session_manager]
|
||||
enabled = true # Starts SessionManager service: <true|false>
|
||||
switch_type = opensips # Defines the type of switch behind: <freeswitch>
|
||||
|
||||
[opensips]
|
||||
listen_udp = :2020 # Address where to listen for datagram events coming from OpenSIPS
|
||||
mi_addr = 172.16.254.77:8020 # Adress where to reach OpenSIPS mi_datagram module
|
||||
|
||||
[mailer]
|
||||
# server = localhost # The server to use when sending emails out
|
||||
# auth_user = cgrates # Authenticate to email server using this user
|
||||
# auth_passwd = CGRateS.org # Authenticate to email server with this password
|
||||
# from_address = cgr-mailer@localhost.localdomain # From address used when sending emails out
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user