Allow configurable event field updates in ARI sessions

This commit is contained in:
ionutboangiu
2024-12-04 13:36:10 +02:00
committed by Dan Christian Bogos
parent a832ea9d7c
commit 6ec0cfb4bc
6 changed files with 80 additions and 35 deletions

View File

@@ -249,8 +249,10 @@ func (sma *AsteriskAgent) handleChannelStateChange(ev *SMAsteriskEvent) {
if !hasIt { // Not handled by us
return
}
// Update the cached event with new channel state.
sma.evCacheMux.Lock()
err := ev.UpdateCGREvent(cgrEvDisp.CGREvent) // Updates the event directly in the cache
err := ev.UpdateCGREvent(cgrEvDisp.CGREvent, sma.cgrCfg.AsteriskAgentCfg().AlterableFields)
sma.evCacheMux.Unlock()
if err != nil {
sma.hangupChannel(ev.ChannelID(),
@@ -258,6 +260,7 @@ func (sma *AsteriskAgent) handleChannelStateChange(ev *SMAsteriskEvent) {
utils.AsteriskAgent, err.Error(), ev.ChannelID()))
return
}
// populate init session args
initSessionArgs := ev.V1InitSessionArgs(*cgrEvDisp)
if initSessionArgs == nil {
@@ -289,8 +292,10 @@ func (sma *AsteriskAgent) handleChannelDestroyed(ev *SMAsteriskEvent) {
if !hasIt { // Not handled by us
return
}
// Update the cached event with new channel state.
sma.evCacheMux.Lock()
err := ev.UpdateCGREvent(cgrEvDisp.CGREvent) // Updates the event directly in the cache
err := ev.UpdateCGREvent(cgrEvDisp.CGREvent, sma.cgrCfg.AsteriskAgentCfg().AlterableFields)
sma.evCacheMux.Unlock()
if err != nil {
utils.Logger.Warning(
@@ -298,6 +303,7 @@ func (sma *AsteriskAgent) handleChannelDestroyed(ev *SMAsteriskEvent) {
utils.AsteriskAgent, err.Error(), ev.ChannelID()))
return
}
// populate terminate session args
tsArgs := ev.V1TerminateSessionArgs(*cgrEvDisp)
if tsArgs == nil {

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
import (
"errors"
"fmt"
"strings"
@@ -186,7 +187,8 @@ func (smaEv *SMAsteriskEvent) ExtraParameters() (extraParams map[string]string)
return
}
func (smaEv *SMAsteriskEvent) UpdateCGREvent(cgrEv *utils.CGREvent) error {
// UpdateCGREvent updates a previously cached CGREvent (from StasisStart) with data from a new ARI event.
func (smaEv *SMAsteriskEvent) UpdateCGREvent(cgrEv *utils.CGREvent, alterableFields []string) error {
resCGREv := *cgrEv
switch smaEv.EventType() {
case ARIChannelStateChange:
@@ -212,6 +214,30 @@ func (smaEv *SMAsteriskEvent) UpdateCGREvent(cgrEv *utils.CGREvent) error {
}
}
}
// Update fields specified in alterableFields. Each field can be in the format:
// - "path.to.field": copies the field value using the last path element as key
// - "path.to.field:alias": copies the field value using the alias as key
if len(alterableFields) != 0 {
ms := utils.MapStorage(smaEv.ariEv)
for _, field := range alterableFields {
path, alias, hasAlias := strings.Cut(field, utils.InInFieldSep)
pathElems := strings.Split(path, utils.NestingSep)
fieldVal, err := ms.FieldAsString(pathElems)
if errors.Is(err, utils.ErrNotFound) {
continue
}
if err != nil {
return err
}
fieldKey := pathElems[len(pathElems)-1]
if hasAlias {
fieldKey = alias
}
resCGREv.Event[fieldKey] = fieldVal
}
}
*cgrEv = resCGREv
return nil
}

View File

@@ -369,15 +369,19 @@ const CGRATES_CFG_JSON = `
"asterisk_agent": {
"enabled": false, // starts the Asterisk agent: <true|false>
"enabled": false, // starts the Asterisk agent: <true|false>
"sessions_conns": ["*internal"],
"create_cdr": false, // create CDR out of events and sends it to CDRS component
"asterisk_conns":[ // instantiate connections to multiple Asterisk servers
{"address": "127.0.0.1:8088", "user": "cgrates", "password": "CGRateS.org", "connect_attempts": 3,"reconnects": 5}
],
"create_cdr": false, // create CDR out of events and sends it to CDRS component
//"alterable_fields": [], // additional fields to update from ARI events, optionally with :alias
"asterisk_conns": [{ // instantiate connections to multiple Asterisk servers
"address": "127.0.0.1:8088",
"user": "cgrates",
"password": "CGRateS.org",
"connect_attempts": 3,
"reconnects": 5
}]
},
"freeswitch_agent": {
"enabled": false, // starts the FreeSWITCH agent: <true|false>
"sessions_conns": ["*internal"],

View File

@@ -268,10 +268,11 @@ type AstConnJsonCfg struct {
}
type AsteriskAgentJsonCfg struct {
Enabled *bool
Sessions_conns *[]string
Create_cdr *bool
Asterisk_conns *[]*AstConnJsonCfg
Enabled *bool
Sessions_conns *[]string
Create_cdr *bool
Alterable_fields *[]string
Asterisk_conns *[]*AstConnJsonCfg
}
type CacheParamJsonCfg struct {

View File

@@ -20,6 +20,7 @@ package config
import (
"fmt"
"slices"
"strconv"
"strings"
"time"
@@ -598,10 +599,11 @@ func (aConnCfg *AsteriskConnCfg) AsMapInterface() map[string]any {
}
type AsteriskAgentCfg struct {
Enabled bool
SessionSConns []string
CreateCDR bool
AsteriskConns []*AsteriskConnCfg
Enabled bool
SessionSConns []string
CreateCDR bool
AlterableFields []string
AsteriskConns []*AsteriskConnCfg
}
func (aCfg *AsteriskAgentCfg) loadFromJsonCfg(jsnCfg *AsteriskAgentJsonCfg) (err error) {
@@ -625,6 +627,9 @@ func (aCfg *AsteriskAgentCfg) loadFromJsonCfg(jsnCfg *AsteriskAgentJsonCfg) (err
if jsnCfg.Create_cdr != nil {
aCfg.CreateCDR = *jsnCfg.Create_cdr
}
if jsnCfg.Alterable_fields != nil {
aCfg.AlterableFields = *jsnCfg.Alterable_fields
}
if jsnCfg.Asterisk_conns != nil {
aCfg.AsteriskConns = make([]*AsteriskConnCfg, len(*jsnCfg.Asterisk_conns))
for i, jsnAConn := range *jsnCfg.Asterisk_conns {
@@ -652,9 +657,10 @@ func (aCfg *AsteriskAgentCfg) AsMapInterface() map[string]any {
}
return map[string]any{
utils.EnabledCfg: aCfg.Enabled,
utils.SessionSConnsCfg: sessionSConns,
utils.CreateCDRCfg: aCfg.CreateCDR,
utils.AsteriskConnsCfg: conns,
utils.EnabledCfg: aCfg.Enabled,
utils.SessionSConnsCfg: sessionSConns,
utils.CreateCDRCfg: aCfg.CreateCDR,
utils.AlterableFieldsCfg: slices.Clone(aCfg.AlterableFields),
utils.AsteriskConnsCfg: conns,
}
}

View File

@@ -497,15 +497,17 @@ func TestAsteriskAgentCfgAsMapInterface(t *testing.T) {
"enabled": true,
"sessions_conns": ["*internal"],
"create_cdr": false,
"alterable_fields": ["field1", "field2"],
"asterisk_conns":[
{"address": "127.0.0.1:8088", "user": "cgrates", "password": "CGRateS.org", "connect_attempts": 3,"reconnects": 5}
],
},
}`
eMap := map[string]any{
"enabled": true,
"sessions_conns": []string{"*internal"},
"create_cdr": false,
"enabled": true,
"sessions_conns": []string{"*internal"},
"create_cdr": false,
utils.AlterableFieldsCfg: []string{"field1", "field2"},
"asterisk_conns": []map[string]any{
{"alias": "", "address": "127.0.0.1:8088", "user": "cgrates", "password": "CGRateS.org", "connect_attempts": 3, "reconnects": 5},
},
@@ -517,7 +519,7 @@ func TestAsteriskAgentCfgAsMapInterface(t *testing.T) {
} else if err = asagcfg.loadFromJsonCfg(jsnAsAgCfg); err != nil {
t.Error(err)
} else if rcv := asagcfg.AsMapInterface(); !reflect.DeepEqual(eMap, rcv) {
t.Errorf("\nExpected: %+v\nReceived: %+v", utils.ToJSON(eMap), utils.ToJSON(rcv))
t.Errorf("expected: %s, received: %s", utils.ToJSON(eMap), utils.ToJSON(rcv))
}
}
@@ -912,9 +914,10 @@ func TestSMConfigAsteriskAgentCfgloadFromJsonCfg(t *testing.T) {
func TestSMConfigAsteriskAgentCfgAsMapInterface(t *testing.T) {
str := "test"
aCfg := &AsteriskAgentCfg{
Enabled: false,
SessionSConns: []string{str},
CreateCDR: false,
Enabled: false,
SessionSConns: []string{str},
CreateCDR: false,
AlterableFields: []string{"field1", "field2"},
AsteriskConns: []*AsteriskConnCfg{
{
Alias: str,
@@ -927,15 +930,14 @@ func TestSMConfigAsteriskAgentCfgAsMapInterface(t *testing.T) {
},
}
exp := map[string]any{
utils.EnabledCfg: aCfg.Enabled,
utils.SessionSConnsCfg: []string{str},
utils.CreateCDRCfg: aCfg.CreateCDR,
utils.AsteriskConnsCfg: []map[string]any{aCfg.AsteriskConns[0].AsMapInterface()},
utils.EnabledCfg: aCfg.Enabled,
utils.SessionSConnsCfg: []string{str},
utils.CreateCDRCfg: aCfg.CreateCDR,
utils.AlterableFieldsCfg: []string{"field1", "field2"},
utils.AsteriskConnsCfg: []map[string]any{aCfg.AsteriskConns[0].AsMapInterface()},
}
rcv := aCfg.AsMapInterface()
if !reflect.DeepEqual(exp, rcv) {
if rcv := aCfg.AsMapInterface(); !reflect.DeepEqual(exp, rcv) {
t.Errorf("expected %s, received %s", utils.ToJSON(exp), utils.ToJSON(rcv))
}
}