From 04c8c10877b5fea8b0ed6cfd7edaa7b2aeab7fdf Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 13 Nov 2015 19:11:38 +0100 Subject: [PATCH] DiameterAgent config skel --- config/config.go | 15 ++++++ config/config_defaults.go | 30 ++++++++++++ config/config_json.go | 13 ++++++ config/config_json_test.go | 49 ++++++++++++++++++++ config/daconfig.go | 95 ++++++++++++++++++++++++++++++++++++++ config/libconfig_json.go | 17 +++++++ utils/consts.go | 1 + utils/coreutils.go | 17 ++++++- 8 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 config/daconfig.go diff --git a/config/config.go b/config/config.go index 7b12732db..26a3d51de 100644 --- a/config/config.go +++ b/config/config.go @@ -65,6 +65,7 @@ func NewDefaultCGRConfig() (*CGRConfig, error) { cfg.SmFsConfig = new(SmFsConfig) cfg.SmKamConfig = new(SmKamConfig) cfg.SmOsipsConfig = new(SmOsipsConfig) + cfg.DiameterAgentCfg = new(DiameterAgentCfg) cfg.ConfigReloads = make(map[string]chan struct{}) cfg.ConfigReloads[utils.CDRC] = make(chan struct{}, 1) cfg.ConfigReloads[utils.CDRC] <- struct{}{} // Unlock the channel @@ -72,6 +73,8 @@ func NewDefaultCGRConfig() (*CGRConfig, error) { cfg.ConfigReloads[utils.CDRE] <- struct{}{} // Unlock the channel cfg.ConfigReloads[utils.SURETAX] = make(chan struct{}, 1) cfg.ConfigReloads[utils.SURETAX] <- struct{}{} // Unlock the channel + cfg.ConfigReloads[utils.DIAMETER_AGENT] = make(chan struct{}, 1) + cfg.ConfigReloads[utils.DIAMETER_AGENT] <- struct{}{} // Unlock the channel cgrJsonCfg, err := NewCgrJsonCfgFromReader(strings.NewReader(CGRATES_CFG_JSON)) if err != nil { return nil, err @@ -229,6 +232,7 @@ type CGRConfig struct { SmFsConfig *SmFsConfig // SM-FreeSWITCH configuration SmKamConfig *SmKamConfig // SM-Kamailio Configuration SmOsipsConfig *SmOsipsConfig // SM-OpenSIPS Configuration + DiameterAgentCfg *DiameterAgentCfg // DiameterAgent configuration HistoryServer string // Address where to reach the master history server: HistoryServerEnabled bool // Starts History as server: . HistoryDir string // Location on disk where to store history files. @@ -461,6 +465,11 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { return err } + jsnDACfg, err := jsnCfg.DiameterAgentJsonCfg() + if err != nil { + return err + } + jsnHistServCfg, err := jsnCfg.HistServJsonCfg() if err != nil { return err @@ -782,6 +791,12 @@ func (self *CGRConfig) loadFromJsonCfg(jsnCfg *CgrJsonCfg) error { } } + if jsnDACfg != nil { + if err := self.DiameterAgentCfg.loadFromJsonCfg(jsnDACfg); err != nil { + return err + } + } + if jsnHistServCfg != nil { if jsnHistServCfg.Enabled != nil { self.HistoryServerEnabled = *jsnHistServCfg.Enabled diff --git a/config/config_defaults.go b/config/config_defaults.go index 92579101e..f3471a1fe 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -259,6 +259,36 @@ const CGRATES_CFG_JSON = ` }, +"diameter_agent": { + "enabled": false, // enables the diameter agent: + "listen": "127.0.0.1:3868", // address where to listen for diameter requests + "timezone": "", // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> + "request_processors": [ + { + "id": "*default", // Identifier of this processor + "dry_run": false, // do not send the CDRs to CDRS, just parse them + "request_filter": "Subscription-Id>Subscription-Type(0)", // filter requests processed by this processor + "continue_on_success": false, // continue to the next template if executed + "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + {"tag": "tor", "cdr_field_id": "TOR", "type": "cdrfield", "value": "^*voice", "mandatory": true}, + {"tag": "accid", "cdr_field_id": "AccId", "type": "cdrfield", "value": "Session-Id", "mandatory": true}, + {"tag": "reqtype", "cdr_field_id": "ReqType", "type": "cdrfield", "value": "^*users", "mandatory": true}, + {"tag": "direction", "cdr_field_id": "Direction", "type": "cdrfield", "value": "^*out", "mandatory": true}, + {"tag": "tenant", "cdr_field_id": "Tenant", "type": "cdrfield", "value": "^*users", "mandatory": true}, + {"tag": "category", "cdr_field_id": "Category", "type": "cdrfield", "value": "^call_;~Calling-Vlr-Number:s/^$/33000/;~Calling-Vlr-Number:s/^(\\d{5})/${1}/", "mandatory": true}, + {"tag": "account", "cdr_field_id": "Account", "type": "cdrfield", "value": "^*users", "mandatory": true}, + {"tag": "subject", "cdr_field_id": "Subject", "type": "cdrfield", "value": "^*users", "mandatory": true}, + {"tag": "destination", "cdr_field_id": "Destination", "type": "cdrfield", "value": "Real-Called-Number", "mandatory": true}, + {"tag": "setup_time", "cdr_field_id": "SetupTime", "type": "cdrfield", "value": "Event-Time", "mandatory": true}, + {"tag": "answer_time", "cdr_field_id": "AnswerTime", "type": "cdrfield", "value": "Event-Time", "mandatory": true}, + {"tag": "usage", "cdr_field_id": "Usage", "type": "cdrfield", "value": "CC-Time", "mandatory": true}, + {"tag": "subscriber_id", "cdr_field_id": "SubscriberId", "type": "cdrfield", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, + ], + }, + ], +}, + + "historys": { "enabled": false, // starts History service: . "history_dir": "/var/log/cgrates/history", // location on disk where to store history files. diff --git a/config/config_json.go b/config/config_json.go index 17616f653..ff7dd366d 100644 --- a/config/config_json.go +++ b/config/config_json.go @@ -48,6 +48,7 @@ const ( FS_JSN = "freeswitch" KAMAILIO_JSN = "kamailio" OSIPS_JSN = "opensips" + DA_JSN = "diameter_agent" HISTSERV_JSN = "historys" PUBSUBSERV_JSN = "pubsubs" ALIASESSERV_JSN = "aliases" @@ -247,6 +248,18 @@ func (self CgrJsonCfg) SmOsipsJsonCfg() (*SmOsipsJsonCfg, error) { return cfg, nil } +func (self CgrJsonCfg) DiameterAgentJsonCfg() (*DiameterAgentJsonCfg, error) { + rawCfg, hasKey := self[DA_JSN] + if !hasKey { + return nil, nil + } + cfg := new(DiameterAgentJsonCfg) + if err := json.Unmarshal(*rawCfg, cfg); err != nil { + return nil, err + } + return cfg, nil +} + func (self CgrJsonCfg) HistServJsonCfg() (*HistServJsonCfg, error) { rawCfg, hasKey := self[HISTSERV_JSN] if !hasKey { diff --git a/config/config_json_test.go b/config/config_json_test.go index 9f45c1c7c..4685ce464 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -414,6 +414,55 @@ func TestSmOsipsJsonCfg(t *testing.T) { } } +func TestDiameterAgentJsonCfg(t *testing.T) { + eCfg := &DiameterAgentJsonCfg{ + Enabled: utils.BoolPointer(false), + Listen: utils.StringPointer("127.0.0.1:3868"), + Timezone: utils.StringPointer(""), + Request_processors: &[]*DARequestProcessorJsnCfg{ + &DARequestProcessorJsnCfg{ + Id: utils.StringPointer("*default"), + Dry_run: utils.BoolPointer(false), + Request_filter: utils.StringPointer("Subscription-Id>Subscription-Type(0)"), + Continue_on_success: utils.BoolPointer(false), + Content_fields: &[]*CdrFieldJsonCfg{ + &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Cdr_field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^*voice"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("accid"), Cdr_field_id: utils.StringPointer(utils.ACCID), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("Session-Id"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("reqtype"), Cdr_field_id: utils.StringPointer(utils.REQTYPE), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("direction"), Cdr_field_id: utils.StringPointer(utils.DIRECTION), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^*out"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("tenant"), Cdr_field_id: utils.StringPointer(utils.TENANT), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("category"), Cdr_field_id: utils.StringPointer(utils.CATEGORY), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^call_;~Calling-Vlr-Number:s/^$/33000/;~Calling-Vlr-Number:s/^(\\d{5})/${1}/"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("account"), Cdr_field_id: utils.StringPointer(utils.ACCOUNT), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("subject"), Cdr_field_id: utils.StringPointer(utils.SUBJECT), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("^*users"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("destination"), Cdr_field_id: utils.StringPointer(utils.DESTINATION), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("Real-Called-Number"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("setup_time"), Cdr_field_id: utils.StringPointer(utils.SETUP_TIME), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("Event-Time"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("answer_time"), Cdr_field_id: utils.StringPointer(utils.ANSWER_TIME), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("Event-Time"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("usage"), Cdr_field_id: utils.StringPointer(utils.USAGE), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("CC-Time"), Mandatory: utils.BoolPointer(true)}, + &CdrFieldJsonCfg{Tag: utils.StringPointer("subscriber_id"), Cdr_field_id: utils.StringPointer("SubscriberId"), Type: utils.StringPointer(utils.CDRFIELD), + Value: utils.StringPointer("Subscription-Id>Subscription-Id-Data"), Mandatory: utils.BoolPointer(true)}, + }, + }, + }, + } + if cfg, err := dfCgrJsonCfg.DiameterAgentJsonCfg(); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eCfg, cfg) { + t.Error("Received: ", cfg) + } +} + func TestDfHistServJsonCfg(t *testing.T) { eCfg := &HistServJsonCfg{ Enabled: utils.BoolPointer(false), diff --git a/config/daconfig.go b/config/daconfig.go new file mode 100644 index 000000000..2c2691aba --- /dev/null +++ b/config/daconfig.go @@ -0,0 +1,95 @@ +/* +Real-time Charging System for Telecom & ISP environments +Copyright (C) 2012-2015 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 +*/ + +package config + +import ( + //"time" + + "github.com/cgrates/cgrates/utils" +) + +type DiameterAgentCfg struct { + Enabled bool // enables the diameter agent: + Listen string // address where to listen for diameter requests + Timezone string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> + RequestProcessors []*DARequestProcessor +} + +func (self *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) error { + if jsnCfg == nil { + return nil + } + if jsnCfg.Enabled != nil { + self.Enabled = *jsnCfg.Enabled + } + if jsnCfg.Listen != nil { + self.Listen = *jsnCfg.Listen + } + if jsnCfg.Timezone != nil { + self.Timezone = *jsnCfg.Timezone + } + if jsnCfg.Request_processors != nil { + for _, reqProcJsn := range *jsnCfg.Request_processors { + rp := new(DARequestProcessor) + for _, rpSet := range self.RequestProcessors { + if reqProcJsn.Id != nil && rpSet.Id == *reqProcJsn.Id { + rp = rpSet // Will load data into the one set + break + } + } + if err := rp.loadFromJsonCfg(reqProcJsn); err != nil { + return nil + } + } + } + return nil +} + +// One Diameter request processor configuration +type DARequestProcessor struct { + Id string + DryRun bool + RequestFilter utils.RSRFields + ContinueOnSuccess bool + ContentFields []*CfgCdrField +} + +func (self *DARequestProcessor) loadFromJsonCfg(jsnCfg *DARequestProcessorJsnCfg) error { + if jsnCfg == nil { + return nil + } + if jsnCfg.Id != nil { + self.Id = *jsnCfg.Id + } + if jsnCfg.Dry_run != nil { + self.DryRun = *jsnCfg.Dry_run + } + var err error + if jsnCfg.Request_filter != nil { + if self.RequestFilter, err = utils.ParseRSRFields(*jsnCfg.Request_filter, utils.INFIELD_SEP); err != nil { + return err + } + } + if jsnCfg.Content_fields != nil { + if self.ContentFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Content_fields); err != nil { + return err + } + } + return nil +} diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 53fbfed98..fb0970f67 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -235,6 +235,23 @@ type OsipsConnJsonCfg struct { Reconnects *int } +// DiameterAgent configuration +type DiameterAgentJsonCfg struct { + Enabled *bool // enables the diameter agent: + Listen *string // address where to listen for diameter requests + Timezone *string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> + Request_processors *[]*DARequestProcessorJsnCfg +} + +// One Diameter request processor configuration +type DARequestProcessorJsnCfg struct { + Id *string + Dry_run *bool + Request_filter *string + Continue_on_success *bool + Content_fields *[]*CdrFieldJsonCfg +} + // History server config section type HistServJsonCfg struct { Enabled *bool diff --git a/utils/consts.go b/utils/consts.go index e36add38c..d3e491ce1 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -239,6 +239,7 @@ const ( EXTRA_FIELDS = "ExtraFields" META_SURETAX = "*sure_tax" SURETAX = "suretax" + DIAMETER_AGENT = "diameter_agent" COUNTER_EVENT = "*event" COUNTER_BALANCE = "*balance" EVENT_NAME = "EventName" diff --git a/utils/coreutils.go b/utils/coreutils.go index ccd4707d5..6a2286f0a 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -20,9 +20,10 @@ package utils import ( "archive/zip" - + "bytes" "crypto/rand" "crypto/sha1" + "encoding/gob" "encoding/json" "errors" "fmt" @@ -406,3 +407,17 @@ func ConvertIfaceToString(fld interface{}) (string, bool) { } return strVal, converted } + +// Simple object cloner, b should be a pointer towards a value into which we want to decode +func Clone(a, b interface{}) error { + buff := new(bytes.Buffer) + enc := gob.NewEncoder(buff) + dec := gob.NewDecoder(buff) + if err := enc.Encode(a); err != nil { + return err + } + if err := dec.Decode(b); err != nil { + return err + } + return nil +}