From d2414111cb92371fff80f3b6ac00979d917906d3 Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 4 Jan 2019 18:33:46 +0100 Subject: [PATCH] Diameter ASR implementation --- agents/diam_it_test.go | 102 ++++++++++++++++++++++- agents/diamagent.go | 8 +- config/config_defaults.go | 30 +++---- config/config_json_test.go | 33 ++++---- config/diametercfg.go | 4 +- data/conf/samples/diamagent/cgrates.json | 4 +- data/conf/samples/diamagent/tests.json | 46 ++++++++++ 7 files changed, 185 insertions(+), 42 deletions(-) create mode 100644 data/conf/samples/diamagent/tests.json diff --git a/agents/diam_it_test.go b/agents/diam_it_test.go index 892c0b3b1..9db362f09 100644 --- a/agents/diam_it_test.go +++ b/agents/diam_it_test.go @@ -87,6 +87,15 @@ func TestDiamItMaxConn(t *testing.T) { t.Run(diamConfigDIR, testDiamItKillEngine) } +func TestDiamItSessionDisconnect(t *testing.T) { + diamConfigDIR = "diamagent" + for _, stest := range sTestsDiam[:7] { + t.Run(diamConfigDIR, stest) + } + t.Run(diamConfigDIR, testDiamInitWithSessionDisconnect) + t.Run(diamConfigDIR, testDiamItKillEngine) +} + func testDiamItInitCfg(t *testing.T) { daCfgPath = path.Join(*dataDir, "conf", "samples", diamConfigDIR) // Init config first @@ -150,7 +159,7 @@ func testDiamItTPFromFolder(t *testing.T) { if err := apierRpc.Call("ApierV2.LoadTariffPlanFromFolder", attrs, &loadInst); err != nil { t.Error(err) } - time.Sleep(time.Duration(1000) * time.Millisecond) // Give time for scheduler to execute topups + time.Sleep(time.Duration(1 * time.Second)) // Give time for scheduler to execute topups } func testDiamItDryRun(t *testing.T) { @@ -721,6 +730,97 @@ func testDiamItCCRSMS(t *testing.T) { } } +func testDiamInitWithSessionDisconnect(t *testing.T) { + attrSetBalance := utils.AttrSetBalance{Tenant: "cgrates.org", + Account: "testDiamInitWithSessionDisconnect", + BalanceType: utils.VOICE, + BalanceID: utils.StringPointer("testDiamInitWithSessionDisconnect"), + Value: utils.Float64Pointer(1 * float64(time.Second)), + RatingSubject: utils.StringPointer("*zero1ms")} + var reply string + if err := apierRpc.Call("ApierV2.SetBalance", attrSetBalance, &reply); err != nil { + t.Error(err) + } else if reply != utils.OK { + t.Errorf("Received: %s", reply) + } + sessID := "bb97be2b9f37c2be9614fff71c8b1d08bdisconnect" + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String(sessID)) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("192.168.1.1")) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(1)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) + m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("testSessionDisconnect")) + m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 4, 14, 42, 20, 0, time.UTC))) + m.NewAVP(avp.UserName, avp.Mbit, 0, datatype.UTF8String("testDiamInitWithSessionDisconnect")) + m.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("testDiamInitWithSessionDisconnect")), // Subscription-Id-Data + }}) + m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) + m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(0))}}) + m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String("1001")), // Calling-Party-Address + diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String("1002")), // Called-Party-Address + diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String("1002")), // Real-Called-Number + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // Charge-Flow-Type + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-Vlr-Number + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-CellID-Or-SAI + diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String("")), // Bearer-Capability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08bdisconnect")), // Call-Reference-Number + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("")), // MSC-Address + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(0)), // Time-Zone + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("")), // Called-Party-NP + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("")), // SSP-Time + }, + }), + }}) + if err := diamClnt.SendMessage(m); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2001" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + time.Sleep(time.Duration(2000 * time.Millisecond)) + msg = diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + if avps, err := msg.FindAVPsWithPath([]interface{}{"Session-Id"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != sessID { + t.Errorf("expecting: %s, received: <%s>", sessID, val) + } +} + func testDiamItKillEngine(t *testing.T) { if err := engine.KillEngine(1000); err != nil { t.Error(err) diff --git a/agents/diamagent.go b/agents/diamagent.go index 236efd096..6a812848e 100644 --- a/agents/diamagent.go +++ b/agents/diamagent.go @@ -162,7 +162,7 @@ func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) { return } // cache message for ASR - if da.cgrCfg.DiameterAgentCfg().ASRTempalte != "" { + if da.cgrCfg.DiameterAgentCfg().ASRTemplate != "" { sessID, err := diamDP.FieldAsString([]string{"Session-Id"}) if err != nil { utils.Logger.Warning( @@ -172,7 +172,7 @@ func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) { } // cache message data needed for building up the ASR engine.Cache.Set(utils.CacheDiameterMessages, sessID, &diamMsgData{c, m, reqVars}, - nil, false, utils.NonTransactional) + nil, true, utils.NonTransactional) } // handle MaxActiveReqs if da.cgrCfg.DiameterAgentCfg().MaxActiveReqs != -1 { @@ -396,14 +396,14 @@ func (da *DiameterAgent) V1DisconnectSession(args utils.AttrDisconnectSession, r dmd.vars, nil, nil, da.cgrCfg.GeneralCfg().DefaultTenant, da.cgrCfg.GeneralCfg().DefaultTimezone, da.filterS) - nM, err := aReq.AsNavigableMap(da.cgrCfg.DiameterAgentCfg().Templates[da.cgrCfg.DiameterAgentCfg().ASRTempalte]) + nM, err := aReq.AsNavigableMap(da.cgrCfg.DiameterAgentCfg().Templates[da.cgrCfg.DiameterAgentCfg().ASRTemplate]) if err != nil { utils.Logger.Warning( fmt.Sprintf("<%s> cannot disconnect session with OriginID: <%s>, err: %s", utils.DiameterAgent, ssID, err.Error())) return utils.ErrServerError } - m := diam.NewMessage(dmd.m.Header.CommandCode, 0, dmd.m.Header.ApplicationID, 0, 0, dmd.m.Dictionary()) + m := diam.NewRequest(dmd.m.Header.CommandCode, dmd.m.Header.ApplicationID, dmd.m.Dictionary()) if err = updateDiamMsgFromNavMap(m, nM, da.cgrCfg.GeneralCfg().DefaultTimezone); err != nil { utils.Logger.Warning( fmt.Sprintf("<%s> cannot disconnect session with OriginID: <%s>, err: %s", diff --git a/config/config_defaults.go b/config/config_defaults.go index 8c4de934b..a39c33c4a 100755 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -401,22 +401,22 @@ const CGRATES_CFG_JSON = ` "value": "~*req.CC-Request-Number", "mandatory": true}, ], "*asr": [ - {"tag": "SessionId", "field_id": "Session-Id", "type": "*composed", - "value": "~*req.OriginID", "mandatory": true}, - {"tag": "OriginHost", "field_id": "Origin-Host", "type": "*composed", - "value": "~*vars.DestinationHost", "mandatory": true}, - {"tag": "OriginRealm", "field_id": "Origin-Realm", "type": "*composed", - "value": "~*vars.DestinationRealm", "mandatory": true}, - {"tag": "DestinationRealm", "field_id": "Destination-Realm", "type": "*composed", - "value": "~*vars.OriginRealm", "mandatory": true}, - {"tag": "DestinationHost", "field_id": "Destination-Host", "type": "*composed", - "value": "~*vars.OriginHost", "mandatory": true}, - {"tag": "AuthApplicationId", "field_id": "Auth-Application-Id", "type": "*composed", + {"tag": "SessionId", "field_id": "Session-Id", "type": "*variable", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginHost", "field_id": "Origin-Host", "type": "*variable", + "value": "~*req.Destination-Host", "mandatory": true}, + {"tag": "OriginRealm", "field_id": "Origin-Realm", "type": "*variable", + "value": "~*req.Destination-Realm", "mandatory": true}, + {"tag": "DestinationRealm", "field_id": "Destination-Realm", "type": "*variable", + "value": "~*req.Origin-Realm", "mandatory": true}, + {"tag": "DestinationHost", "field_id": "Destination-Host", "type": "*variable", + "value": "~*req.Origin-Host", "mandatory": true}, + {"tag": "AuthApplicationId", "field_id": "Auth-Application-Id", "type": "*variable", "value": "~*vars.*appid", "mandatory": true}, - {"tag": "UserName", "field_id": "User-Name", "type": "*composed", - "value": "~*req.Account", "mandatory": true}, - {"tag": "OriginStateID", "field_id": "Origin-State-Id", "type": "*composed", - "value": "~*vars.OriginStateID", "mandatory": true}, + {"tag": "UserName", "field_id": "User-Name", "type": "*variable", + "value": "~*req.User-Name", "mandatory": true}, + {"tag": "OriginStateID", "field_id": "Origin-State-Id", "type": "*constant", + "value": "1"}, ] }, "request_processors": [], diff --git a/config/config_json_test.go b/config/config_json_test.go index 9242e1aa5..77c8e641c 100755 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -645,44 +645,43 @@ func TestDiameterAgentJsonCfg(t *testing.T) { utils.MetaASR: { {Tag: utils.StringPointer("SessionId"), Field_id: utils.StringPointer("Session-Id"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*req.OriginID"), + Type: utils.StringPointer(utils.MetaVariable), + Value: utils.StringPointer("~*req.Session-Id"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("OriginHost"), Field_id: utils.StringPointer("Origin-Host"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*vars.DestinationHost"), + Type: utils.StringPointer(utils.MetaVariable), + Value: utils.StringPointer("~*req.Destination-Host"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("OriginRealm"), Field_id: utils.StringPointer("Origin-Realm"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*vars.DestinationRealm"), + Type: utils.StringPointer(utils.MetaVariable), + Value: utils.StringPointer("~*req.Destination-Realm"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("DestinationRealm"), Field_id: utils.StringPointer("Destination-Realm"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*vars.OriginRealm"), + Type: utils.StringPointer(utils.MetaVariable), + Value: utils.StringPointer("~*req.Origin-Realm"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("DestinationHost"), Field_id: utils.StringPointer("Destination-Host"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*vars.OriginHost"), + Type: utils.StringPointer(utils.MetaVariable), + Value: utils.StringPointer("~*req.Origin-Host"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("AuthApplicationId"), Field_id: utils.StringPointer("Auth-Application-Id"), - Type: utils.StringPointer(utils.META_COMPOSED), + Type: utils.StringPointer(utils.MetaVariable), Value: utils.StringPointer("~*vars.*appid"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("UserName"), Field_id: utils.StringPointer("User-Name"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*req.Account"), + Type: utils.StringPointer(utils.MetaVariable), + Value: utils.StringPointer("~*req.User-Name"), Mandatory: utils.BoolPointer(true)}, {Tag: utils.StringPointer("OriginStateID"), - Field_id: utils.StringPointer("Origin-State-Id"), - Type: utils.StringPointer(utils.META_COMPOSED), - Value: utils.StringPointer("~*vars.OriginStateID"), - Mandatory: utils.BoolPointer(true)}, + Field_id: utils.StringPointer("Origin-State-Id"), + Type: utils.StringPointer(utils.META_CONSTANT), + Value: utils.StringPointer("1")}, }, }, Request_processors: &[]*DARequestProcessorJsnCfg{}, diff --git a/config/diametercfg.go b/config/diametercfg.go index be6177cff..3f2ba710d 100644 --- a/config/diametercfg.go +++ b/config/diametercfg.go @@ -33,7 +33,7 @@ type DiameterAgentCfg struct { VendorId int ProductName string MaxActiveReqs int // limit the maximum number of requests processed - ASRTempalte string + ASRTemplate string Templates map[string][]*FCTemplate RequestProcessors []*DARequestProcessor } @@ -77,7 +77,7 @@ func (da *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg, separa da.MaxActiveReqs = *jsnCfg.Max_active_requests } if jsnCfg.Asr_template != nil { - da.ASRTempalte = *jsnCfg.Asr_template + da.ASRTemplate = *jsnCfg.Asr_template } if jsnCfg.Templates != nil { if da.Templates == nil { diff --git a/data/conf/samples/diamagent/cgrates.json b/data/conf/samples/diamagent/cgrates.json index e3ed92240..af7dbd106 100644 --- a/data/conf/samples/diamagent/cgrates.json +++ b/data/conf/samples/diamagent/cgrates.json @@ -67,9 +67,7 @@ "diameter_agent": { "enabled": true, - "sessions_conns": [ - {"address": "127.0.0.1:2012","transport":"*json"} - ], + "asr_template": "*asr", }, } diff --git a/data/conf/samples/diamagent/tests.json b/data/conf/samples/diamagent/tests.json new file mode 100644 index 000000000..b1972afe6 --- /dev/null +++ b/data/conf/samples/diamagent/tests.json @@ -0,0 +1,46 @@ + +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "TestSessionDisconnect", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:1", + "*prefix:*req.Service-Context-Id:testSessionDisconnect"], + "flags": ["*initiate", "*accounts"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*voice"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*variable", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginHost", "field_id": "OriginHost", "type": "*variable", + "value": "~*req.Origin-Host", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", + "type": "*constant", "value": "*prepaid"}, + {"tag": "Category", "field_id": "Category", + "type": "*constant", "value": "call"}, + {"tag": "Account", "field_id": "Account", "type": "*variable", + "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, + {"tag": "Subject", "field_id": "Subject", "type": "*variable", + "value": "~*req.Service-Information.IN-Information.Calling-Party-Address", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*variable", + "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*variable", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*variable", + "value": "~*req.Requested-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, + {"tag": "DebitInterval", "field_id": "CGRDebitInterval", + "type": "*constant", "value": "1s"}, + ], + "reply_fields":[ + {"tag": "CCATemplate", "type": "*template", "value": "*cca"}, + {"tag": "ResultCode", "filters": ["*notempty:*cgrep.Error:"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + {"tag": "GrantedUnits", "field_id": "Granted-Service-Unit.CC-Time", + "filters": ["*gte:*cgrep.MaxUsage:0s"], + "type": "*variable", "value": "~*cgrep.MaxUsage{*duration_seconds&*round:0}", "mandatory": true}, + ], + }, + ], +}, + +} \ No newline at end of file