Update Radius

This commit is contained in:
TeoV
2018-07-19 10:11:50 -04:00
committed by Dan Christian Bogos
parent ebaf5791b3
commit a386f98990
11 changed files with 290 additions and 459 deletions

View File

@@ -151,6 +151,23 @@ func (aReq *AgentRequest) ParseField(
case utils.META_COMPOSED:
out = aReq.composedField(cfgFld.Value)
isString = true
case utils.META_USAGE_DIFFERENCE:
if len(cfgFld.Value) != 2 {
return nil, fmt.Errorf("invalid arguments <%s>", utils.ToJSON(cfgFld.Value))
} else {
strVal1, err := aReq.FieldAsString(strings.Split(cfgFld.Value[0].Id, utils.NestingSep))
strVal2, err := aReq.FieldAsString(strings.Split(cfgFld.Value[1].Id, utils.NestingSep))
tEnd, err := utils.ParseTimeDetectLayout(strVal1, cfgFld.Timezone)
if err != nil {
return "", err
}
tStart, err := utils.ParseTimeDetectLayout(strVal2, cfgFld.Timezone)
if err != nil {
return "", err
}
out = tEnd.Sub(tStart).String()
}
isString = true
}
if isString { // format the string additionally with fmtFieldWidth
out, err = utils.FmtFieldWidth(cfgFld.Tag, out.(string), cfgFld.Width,
@@ -172,17 +189,18 @@ func (ar *AgentRequest) composedField(outTpl utils.RSRFields) (outVal string) {
}
continue
}
valStr, err := ar.FieldAsString(strings.Split(rsrTpl.Id, utils.NestingSep))
if err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> %s",
utils.HTTPAgent, err.Error()))
fmt.Sprintf("<%s> %s FieldAsString %s",
utils.AgentRequest, err.Error(), rsrTpl.Id))
continue
}
if parsed, err := rsrTpl.Parse(valStr); err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> %s",
utils.HTTPAgent, err.Error()))
fmt.Sprintf("<%s> %s Parse ",
utils.AgentRequest, err.Error(), valStr))
} else {
outVal += parsed
}

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
/*
import (
"reflect"
"testing"
@@ -121,8 +120,6 @@ func TestAgReqAsNavigableMap(t *testing.T) {
if mpOut, err := agReq.AsNavigableMap(tplFlds); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eMp, mpOut) {
t.Errorf("expecting: %+v, received: %+v",
eMp.AsMapStringInterface(), mpOut.AsMapStringInterface())
t.Errorf("expecting: %+v, received: %+v", eMp, mpOut)
}
}
*/

View File

@@ -64,6 +64,153 @@ var (
ErrDiameterRatingFailed = errors.New("Diameter rating failed")
)
// processorVars will hold various variables using during request processing
// here so we can define methods on it
type processorVars map[string]interface{}
// hasSubsystems will return true on single subsystem being present in processorVars
func (pv processorVars) hasSubsystems() (has bool) {
for _, k := range []string{utils.MetaAccounts, utils.MetaResources,
utils.MetaSuppliers, utils.MetaAttributes} {
if _, has = pv[k]; has {
return
}
}
return
}
func (pv processorVars) hasVar(k string) (has bool) {
_, has = pv[k]
return
}
// valAsInterface returns the string value for fldName
func (pv processorVars) valAsInterface(fldPath string) (val interface{}, err error) {
fldName := fldPath
if strings.HasPrefix(fldPath, utils.MetaCGRReply) {
fldName = utils.MetaCGRReply
}
if !pv.hasVar(fldName) {
err = errors.New("not found")
return
}
return engine.NewNavigableMap(pv).FieldAsInterface(strings.Split(fldPath, utils.HIERARCHY_SEP))
}
// valAsString returns the string value for fldName
// returns empty if fldName not found
func (pv processorVars) valAsString(fldPath string) (val string, err error) {
fldName := fldPath
if strings.HasPrefix(fldPath, utils.MetaCGRReply) {
fldName = utils.MetaCGRReply
}
if !pv.hasVar(fldName) {
return "", utils.ErrNotFoundNoCaps
}
return engine.NewNavigableMap(pv).FieldAsString(strings.Split(fldPath, utils.HIERARCHY_SEP))
}
// asV1AuthorizeArgs returns the arguments needed by SessionSv1.AuthorizeEvent
func (pv processorVars) asV1AuthorizeArgs(cgrEv *utils.CGREvent) (args *sessions.V1AuthorizeArgs) {
args = &sessions.V1AuthorizeArgs{ // defaults
GetMaxUsage: true,
CGREvent: *cgrEv,
}
if !pv.hasSubsystems() {
return
}
if !pv.hasVar(utils.MetaAccounts) {
args.GetMaxUsage = false
}
if pv.hasVar(utils.MetaResources) {
args.AuthorizeResources = true
}
if pv.hasVar(utils.MetaSuppliers) {
args.GetSuppliers = true
}
if pv.hasVar(utils.MetaAttributes) {
args.GetAttributes = true
}
return
}
// asV1InitSessionArgs returns the arguments used in SessionSv1.InitSession
func (pv processorVars) asV1InitSessionArgs(cgrEv *utils.CGREvent) (args *sessions.V1InitSessionArgs) {
args = &sessions.V1InitSessionArgs{ // defaults
InitSession: true,
CGREvent: *cgrEv,
}
if !pv.hasSubsystems() {
return
}
if !pv.hasVar(utils.MetaAccounts) {
args.InitSession = false
}
if pv.hasVar(utils.MetaResources) {
args.AllocateResources = true
}
if pv.hasVar(utils.MetaAttributes) {
args.GetAttributes = true
}
return
}
// asV1UpdateSessionArgs returns the arguments used in SessionSv1.InitSession
func (pv processorVars) asV1UpdateSessionArgs(cgrEv *utils.CGREvent) (args *sessions.V1UpdateSessionArgs) {
args = &sessions.V1UpdateSessionArgs{ // defaults
UpdateSession: true,
CGREvent: *cgrEv,
}
if !pv.hasSubsystems() {
return
}
if !pv.hasVar(utils.MetaAccounts) {
args.UpdateSession = false
}
if pv.hasVar(utils.MetaAttributes) {
args.GetAttributes = true
}
return
}
// asV1TerminateSessionArgs returns the arguments used in SMGv1.TerminateSession
func (pv processorVars) asV1TerminateSessionArgs(cgrEv *utils.CGREvent) (args *sessions.V1TerminateSessionArgs) {
args = &sessions.V1TerminateSessionArgs{ // defaults
TerminateSession: true,
CGREvent: *cgrEv,
}
if !pv.hasSubsystems() {
return
}
if !pv.hasVar(utils.MetaAccounts) {
args.TerminateSession = false
}
if pv.hasVar(utils.MetaResources) {
args.ReleaseResources = true
}
return
}
func (pv processorVars) asV1ProcessEventArgs(cgrEv *utils.CGREvent) (args *sessions.V1ProcessEventArgs) {
args = &sessions.V1ProcessEventArgs{ // defaults
Debit: true,
CGREvent: *cgrEv,
}
if !pv.hasSubsystems() {
return
}
if !pv.hasVar(utils.MetaAccounts) {
args.Debit = false
}
if pv.hasVar(utils.MetaResources) {
args.AllocateResources = true
}
if pv.hasVar(utils.MetaAttributes) {
args.GetAttributes = true
}
return
}
func loadDictionaries(dictsDir, componentId string) error {
fi, err := os.Stat(dictsDir)
if err != nil {

View File

@@ -19,15 +19,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/sessions"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/radigo"
)
@@ -47,7 +43,7 @@ func attrVendorFromPath(path string) (attrName, vendorName string) {
// radComposedFieldValue extracts the field value out of RADIUS packet
// procVars have priority over packet variables
func radComposedFieldValue(pkt *radigo.Packet,
procVars processorVars, outTpl utils.RSRFields) (outVal string) {
agReq *AgentRequest, outTpl utils.RSRFields) (outVal string) {
for _, rsrTpl := range outTpl {
if rsrTpl.IsStatic() {
if parsed, err := rsrTpl.Parse(""); err != nil {
@@ -59,13 +55,11 @@ func radComposedFieldValue(pkt *radigo.Packet,
}
continue
}
if val, err := procVars.valAsString(rsrTpl.Id); err != nil {
if err.Error() != "not found" {
utils.Logger.Warning(
fmt.Sprintf("<%s> %s",
utils.RadiusAgent, err.Error()))
continue
}
if val, err := agReq.FieldAsString(strings.Split(rsrTpl.Id, utils.NestingSep)); err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> %s",
utils.RadiusAgent, err.Error()))
continue
} else {
if parsed, err := rsrTpl.Parse(val); err != nil {
utils.Logger.Warning(
@@ -90,41 +84,8 @@ func radComposedFieldValue(pkt *radigo.Packet,
return outVal
}
// radMetaHandler handles *handler type in configuration fields
func radMetaHandler(pkt *radigo.Packet, procVars processorVars,
cfgFld *config.CfgCdrField, roundingDecimals int) (outVal string, err error) {
handlerArgs := strings.Split(
radComposedFieldValue(pkt, procVars, cfgFld.Value), utils.HandlerArgSep)
switch cfgFld.HandlerId {
case MetaUsageDifference: // expects tEnd|tStart in the composed val
if len(handlerArgs) != 2 {
return "", errors.New("unexpected number of arguments")
}
tEnd, err := utils.ParseTimeDetectLayout(handlerArgs[0], cfgFld.Timezone)
if err != nil {
return "", err
}
tStart, err := utils.ParseTimeDetectLayout(handlerArgs[1], cfgFld.Timezone)
if err != nil {
return "", err
}
return tEnd.Sub(tStart).String(), nil
case utils.MetaDurationSeconds:
if len(handlerArgs) != 1 {
return "", errors.New("unexpected number of arguments")
}
val, err := utils.ParseDurationWithNanosecs(handlerArgs[0])
if err != nil {
return "", err
}
return strconv.FormatInt(int64(utils.Round(val.Seconds(),
roundingDecimals, utils.ROUNDING_MIDDLE)), 10), nil
}
return
}
// radFieldOutVal formats the field value retrieved from RADIUS packet
func radFieldOutVal(pkt *radigo.Packet, processorVars processorVars,
func radFieldOutVal(pkt *radigo.Packet, agReq *AgentRequest,
cfgFld *config.CfgCdrField) (outVal string, err error) {
// different output based on cgrFld.Type
switch cfgFld.Type {
@@ -134,12 +95,7 @@ func radFieldOutVal(pkt *radigo.Packet, processorVars processorVars,
case utils.META_CONSTANT:
outVal = cfgFld.Value.Id()
case utils.META_COMPOSED:
outVal = radComposedFieldValue(pkt, processorVars, cfgFld.Value)
case utils.META_HANDLER:
if outVal, err = radMetaHandler(pkt, processorVars, cfgFld,
config.CgrConfig().RoundingDecimals); err != nil {
return "", err
}
outVal = radComposedFieldValue(pkt, agReq, cfgFld.Value)
default:
return "", fmt.Errorf("unsupported configuration field type: <%s>", cfgFld.Type)
}
@@ -150,20 +106,10 @@ func radFieldOutVal(pkt *radigo.Packet, processorVars processorVars,
}
// radReplyAppendAttributes appends attributes to a RADIUS reply based on predefined template
func radReplyAppendAttributes(reply *radigo.Packet, procVars map[string]interface{},
func radReplyAppendAttributes(reply *radigo.Packet, agReq *AgentRequest,
cfgFlds []*config.CfgCdrField) (err error) {
for _, cfgFld := range cfgFlds {
passedAllFilters := true
for _, fldFilter := range cfgFld.FieldFilter {
if !radPassesFieldFilter(reply, procVars, fldFilter) {
passedAllFilters = false
break
}
}
if !passedAllFilters {
continue
}
fmtOut, err := radFieldOutVal(reply, procVars, cfgFld)
fmtOut, err := radFieldOutVal(reply, agReq, cfgFld)
if err != nil {
return err
}
@@ -191,12 +137,15 @@ func NewCGRReply(rply engine.NavigableMapper,
return engine.NewNavigableMap(map[string]interface{}{
utils.Error: errRply.Error()}), nil
}
mp, err = rply.AsNavigableMap(nil)
if err != nil {
return nil, err
mp = engine.NewNavigableMap(nil)
if rply != nil {
mp, err = rply.AsNavigableMap(nil)
if err != nil {
return nil, err
}
}
mp.Set([]string{utils.Error}, "", false) // enforce empty error
return mp, nil
return
}
// newRADataProvider constructs a DataProvider
@@ -215,7 +164,7 @@ type radiusDP struct {
// String is part of engine.DataProvider interface
// when called, it will display the already parsed values out of cache
func (pk *radiusDP) String() string {
return ""
return utils.ToJSON(pk)
}
// FieldAsInterface is part of engine.DataProvider interface
@@ -228,6 +177,9 @@ func (pk *radiusDP) FieldAsInterface(fldPath []string) (data interface{}, err er
return
}
err = nil // cancel previous err
if len(pk.req.AttributesWithName(fldPath[0], "")) != 0 {
data = pk.req.AttributesWithName(fldPath[0], "")[0].GetStringValue()
}
pk.cache.Set(fldPath, data, false)
return
}

View File

@@ -28,7 +28,6 @@ import (
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/sessions"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/radigo"
)
@@ -91,47 +90,6 @@ func TestAttrVendorFromPath(t *testing.T) {
}
}
func TestRadPassesFieldFilter(t *testing.T) {
pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org")
if err := pkt.AddAVPWithName("User-Name", "flopsy", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Cisco-NAS-Port", "CGR1", "Cisco"); err != nil {
t.Error(err)
}
if !radPassesFieldFilter(pkt, nil, nil) {
t.Error("not passing empty filter")
}
if !radPassesFieldFilter(pkt, nil,
utils.NewRSRFieldMustCompile("User-Name(flopsy)")) {
t.Error("not passing valid filter")
}
if radPassesFieldFilter(pkt, nil,
utils.NewRSRFieldMustCompile("User-Name(notmatching)")) {
t.Error("passing invalid filter value")
}
if !radPassesFieldFilter(pkt, nil,
utils.NewRSRFieldMustCompile("Cisco>Cisco-NAS-Port(CGR1)")) {
t.Error("not passing valid filter")
}
if radPassesFieldFilter(pkt, nil,
utils.NewRSRFieldMustCompile("Cisco>Cisco-NAS-Port(notmatching)")) {
t.Error("passing invalid filter value")
}
if !radPassesFieldFilter(pkt, processorVars{MetaRadReqType: MetaRadAuth},
utils.NewRSRFieldMustCompile(fmt.Sprintf("%s(%s)", MetaRadReqType, MetaRadAuth))) {
t.Error("not passing valid filter")
}
if radPassesFieldFilter(pkt, processorVars{MetaRadReqType: MetaRadAcctStart},
utils.NewRSRFieldMustCompile(fmt.Sprintf("%s(%s)", MetaRadReqType, MetaRadAuth))) {
t.Error("passing invalid filter")
}
if radPassesFieldFilter(pkt, nil,
utils.NewRSRFieldMustCompile("UnknownField(notmatching)")) {
t.Error("passing invalid filter value")
}
}
func TestRadComposedFieldValue(t *testing.T) {
pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org")
if err := pkt.AddAVPWithName("User-Name", "flopsy", ""); err != nil {
@@ -140,10 +98,13 @@ func TestRadComposedFieldValue(t *testing.T) {
if err := pkt.AddAVPWithName("Cisco-NAS-Port", "CGR1", "Cisco"); err != nil {
t.Error(err)
}
eOut := fmt.Sprintf("%s|flopsy|CGR1", MetaRadAcctStart)
if out := radComposedFieldValue(pkt, processorVars{MetaRadReqType: MetaRadAcctStart},
utils.ParseRSRFieldsMustCompile(fmt.Sprintf("%s;^|;User-Name;^|;Cisco>Cisco-NAS-Port",
MetaRadReqType), utils.INFIELD_SEP)); out != eOut {
agReq := newAgentRequest(nil, nil, "cgrates.org", nil)
agReq.Vars.Set([]string{MetaRadReqType}, MetaRadAcctStart, false)
agReq.Vars.Set([]string{"Cisco"}, "CGR1", false)
agReq.Vars.Set([]string{"User-Name"}, "flopsy", false)
eOut := "*radAcctStart|flopsy|CGR1"
if out := radComposedFieldValue(pkt, agReq,
utils.ParseRSRFieldsMustCompile("*vars.*radReqType;^|;*vars.User-Name;^|;*vars.Cisco", utils.INFIELD_SEP)); out != eOut {
t.Errorf("Expecting: <%s>, received: <%s>", eOut, out)
}
}
@@ -157,263 +118,33 @@ func TestRadFieldOutVal(t *testing.T) {
t.Error(err)
}
eOut := fmt.Sprintf("%s|flopsy|CGR1", MetaRadAcctStart)
agReq := newAgentRequest(nil, nil, "cgrates.org", nil)
agReq.Vars.Set([]string{MetaRadReqType}, MetaRadAcctStart, false)
agReq.Vars.Set([]string{"Cisco"}, "CGR1", false)
agReq.Vars.Set([]string{"User-Name"}, "flopsy", false)
//processorVars{MetaRadReqType: MetaRadAcctStart}
cfgFld := &config.CfgCdrField{Tag: "ComposedTest", Type: utils.META_COMPOSED, FieldId: utils.Destination,
Value: utils.ParseRSRFieldsMustCompile(fmt.Sprintf("%s;^|;User-Name;^|;Cisco>Cisco-NAS-Port", MetaRadReqType), utils.INFIELD_SEP), Mandatory: true}
if outVal, err := radFieldOutVal(pkt, processorVars{MetaRadReqType: MetaRadAcctStart}, cfgFld); err != nil {
Value: utils.ParseRSRFieldsMustCompile("*vars.*radReqType;^|;*vars.User-Name;^|;*vars.Cisco", utils.INFIELD_SEP), Mandatory: true}
if outVal, err := radFieldOutVal(pkt, agReq, cfgFld); err != nil {
t.Error(err)
} else if outVal != eOut {
t.Errorf("Expecting: <%s>, received: <%s>", eOut, outVal)
}
}
func TestRadReqAsCGREvent(t *testing.T) {
pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org")
// Sample minimal packet sent by Kamailio
if err := pkt.AddAVPWithName("Acct-Status-Type", "2", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Service-Type", "15", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-Response-Code", "200", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-Method", "8", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Event-Timestamp", "1497106119", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-From-Tag", "75c2f57b", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-To-Tag", "51585361", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Acct-Session-Id", "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("User-Name", "1001", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Ascend-User-Acct-Time", "1497106115", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("NAS-Port-Id", "5060", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Acct-Delay-Time", "0", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil {
t.Error(err)
}
cfgFlds := []*config.CfgCdrField{
&config.CfgCdrField{Tag: "TOR", FieldId: utils.ToR, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile(utils.VOICE, utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginID", FieldId: utils.OriginID, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Acct-Session-Id;^-;Sip-From-Tag;^-;Sip-To-Tag", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginHost", FieldId: utils.OriginHost, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("NAS-IP-Address", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "RequestType", FieldId: utils.RequestType, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile(utils.META_PREPAID, utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Direction", FieldId: utils.Direction, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile(utils.OUT, utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Tenant", FieldId: utils.Tenant, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile("cgrates.org", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Category", FieldId: utils.Category, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile("call", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Account", FieldId: utils.Account, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("User-Name", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Destination", FieldId: utils.Destination, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Called-Station-Id", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "SetupTime", FieldId: utils.SetupTime, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Ascend-User-Acct-Time", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "AnswerTime", FieldId: utils.AnswerTime, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Ascend-User-Acct-Time", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Usage", FieldId: utils.Usage, Type: utils.META_HANDLER, HandlerId: MetaUsageDifference,
Value: utils.ParseRSRFieldsMustCompile("Event-Timestamp;^|;Ascend-User-Acct-Time", utils.INFIELD_SEP)},
}
eOut := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.Account: "1001",
utils.AnswerTime: "1497106115",
utils.Category: "call",
utils.Destination: "1002",
utils.Direction: utils.META_OUT,
utils.OriginHost: "127.0.0.1",
utils.OriginID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-75c2f57b-51585361",
utils.RequestType: "*prepaid",
utils.SetupTime: "1497106115",
utils.Tenant: "cgrates.org",
utils.ToR: "*voice",
utils.Usage: "4s",
},
}
if outVal, err := radReqAsCGREvent(pkt, processorVars{MetaRadReqType: MetaRadAcctStart}, nil, cfgFlds); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(outVal.Tenant, eOut.Tenant) {
t.Errorf("Expecting: <%s>, received: <%s>", utils.ToJSON(eOut.Tenant), utils.ToJSON(outVal.Tenant))
} else if !reflect.DeepEqual(outVal.Event, eOut.Event) {
t.Errorf("Expecting: <%s>, received: <%s>", utils.ToJSON(eOut.Event), utils.ToJSON(outVal.Event))
}
}
func TestPVAsV1AuthorizeArgs(t *testing.T) {
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.Account: "1001",
utils.AnswerTime: "1497106115",
utils.Category: "call",
utils.Destination: "1002",
utils.Direction: utils.META_OUT,
utils.OriginHost: "127.0.0.1",
utils.OriginID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-75c2f57b-51585361",
utils.RequestType: "*prepaid",
utils.SetupTime: "1497106115",
utils.Tenant: "cgrates.org",
utils.ToR: "*voice",
utils.Usage: "4s",
},
}
expected := &sessions.V1AuthorizeArgs{
GetMaxUsage: true,
CGREvent: *cgrEv,
}
outVal := processorVars{MetaRadReqType: MetaRadAcctStart}.asV1AuthorizeArgs(cgrEv)
if !reflect.DeepEqual(expected, outVal) {
t.Errorf("Expecting: <%s>, received: <%s>", utils.ToJSON(expected), utils.ToJSON(outVal))
}
}
func TestPVAsV1InitSessionArgs(t *testing.T) {
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.Account: "1001",
utils.AnswerTime: "1497106115",
utils.Category: "call",
utils.Destination: "1002",
utils.Direction: utils.META_OUT,
utils.OriginHost: "127.0.0.1",
utils.OriginID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-75c2f57b-51585361",
utils.RequestType: "*prepaid",
utils.SetupTime: "1497106115",
utils.Tenant: "cgrates.org",
utils.ToR: "*voice",
utils.Usage: "4s",
},
}
expected := &sessions.V1InitSessionArgs{
InitSession: true,
CGREvent: *cgrEv,
}
outVal := processorVars{MetaRadReqType: MetaRadAcctStart}.asV1InitSessionArgs(cgrEv)
if !reflect.DeepEqual(expected, outVal) {
t.Errorf("Expecting: <%s>, received: <%s>", utils.ToJSON(expected), utils.ToJSON(outVal))
}
eInitArgs := &sessions.V1InitSessionArgs{
InitSession: false,
AllocateResources: true,
GetAttributes: true,
CGREvent: *cgrEv,
}
initArgs := processorVars{MetaRadReqType: MetaRadAcctStart, utils.MetaResources: true,
utils.MetaAttributes: true}.asV1InitSessionArgs(cgrEv)
if !reflect.DeepEqual(eInitArgs, initArgs) {
t.Errorf("expecting: %+v, received: %+v", eInitArgs, initArgs)
}
}
func TestPVAsV1UpdateSessionArgs(t *testing.T) {
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.Account: "1001",
utils.AnswerTime: "1497106115",
utils.Category: "call",
utils.Destination: "1002",
utils.Direction: utils.META_OUT,
utils.OriginHost: "127.0.0.1",
utils.OriginID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-75c2f57b-51585361",
utils.RequestType: "*prepaid",
utils.SetupTime: "1497106115",
utils.Tenant: "cgrates.org",
utils.ToR: "*voice",
utils.Usage: "4s",
},
}
expected := &sessions.V1UpdateSessionArgs{
UpdateSession: true,
CGREvent: *cgrEv,
}
outVal := processorVars{MetaRadReqType: MetaRadAcctStart}.asV1UpdateSessionArgs(cgrEv)
if !reflect.DeepEqual(expected, outVal) {
t.Errorf("Expecting: <%s>, received: <%s>", utils.ToJSON(expected), utils.ToJSON(outVal))
}
}
func TestPVAsTerminateSessionArgs(t *testing.T) {
cgrEv := &utils.CGREvent{
Tenant: "cgrates.org",
ID: utils.UUIDSha1Prefix(),
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.Account: "1001",
utils.AnswerTime: "1497106115",
utils.Category: "call",
utils.Destination: "1002",
utils.Direction: utils.META_OUT,
utils.OriginHost: "127.0.0.1",
utils.OriginID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-75c2f57b-51585361",
utils.RequestType: "*prepaid",
utils.SetupTime: "1497106115",
utils.Tenant: "cgrates.org",
utils.ToR: "*voice",
utils.Usage: "4s",
},
}
expected := &sessions.V1TerminateSessionArgs{
TerminateSession: true,
CGREvent: *cgrEv,
}
outVal := processorVars{MetaRadReqType: MetaRadAcctStart}.asV1TerminateSessionArgs(cgrEv)
if !reflect.DeepEqual(expected, outVal) {
t.Errorf("Expecting: <%s>, received: <%s>", utils.ToJSON(expected), utils.ToJSON(outVal))
}
}
func TestRadReplyAppendAttributes(t *testing.T) {
rply := radigo.NewPacket(radigo.AccessRequest, 2, dictRad, coder, "CGRateS.org").Reply()
rplyFlds := []*config.CfgCdrField{
&config.CfgCdrField{Tag: "ReplyCode", FieldId: MetaRadReplyCode, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("*cgrReply>Attributes>RadReply", utils.INFIELD_SEP)},
Value: utils.ParseRSRFieldsMustCompile("*cgrReply.Attributes.RadReply", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Acct-Session-Time", FieldId: "Acct-Session-Time", Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("*cgrReply>MaxUsage{*duration_seconds}", utils.INFIELD_SEP)},
Value: utils.ParseRSRFieldsMustCompile("*cgrReply.MaxUsage{*duration_seconds}", utils.INFIELD_SEP)},
}
procVars := make(processorVars)
procVars[utils.MetaCGRReply] = map[string]interface{}{
utils.CapAttributes: map[string]interface{}{
"RadReply": "AccessAccept",
utils.Account: "1001",
},
utils.CapMaxUsage: time.Duration(time.Hour),
}
if err := radReplyAppendAttributes(rply, procVars, rplyFlds); err != nil {
agReq := newAgentRequest(nil, nil, "cgrates.org", nil)
agReq.CGRReply.Set([]string{utils.CapMaxUsage}, time.Duration(time.Hour), false)
agReq.CGRReply.Set([]string{utils.CapAttributes, "RadReply"}, "AccessAccept", false)
agReq.CGRReply.Set([]string{utils.CapAttributes, utils.Account}, "1001", false)
if err := radReplyAppendAttributes(rply, agReq, rplyFlds); err != nil {
t.Error(err)
}
if rply.Code != radigo.AccessAccept {

View File

@@ -19,8 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
import (
"errors"
"fmt"
"strconv"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
@@ -88,6 +88,7 @@ func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err e
return
}
agReq := newAgentRequest(dcdr, ra.tenantCfg, ra.cgrCfg.DefaultTenant, ra.filterS)
agReq.Vars.Set([]string{MetaRadReqType}, utils.StringToInterface(MetaRadAuth), true)
rpl = req.Reply()
rpl.Code = radigo.AccessAccept
var processed bool
@@ -101,12 +102,12 @@ func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err e
}
}
if err != nil {
utils.Logger.Err(fmt.Sprintf("<%s> error: <%s> ignoring request: %s, process vars: %+v",
utils.RadiusAgent, err.Error(), utils.ToJSON(req), procVars))
utils.Logger.Err(fmt.Sprintf("<%s> error: <%s> ignoring request: %s, agentRequest: %+v",
utils.RadiusAgent, err.Error(), utils.ToJSON(req), utils.ToJSON(agReq)))
return nil, nil
} else if !processed {
utils.Logger.Err(fmt.Sprintf("<%s> no request processor enabled, ignoring request %s, process vars: %+v",
utils.RadiusAgent, utils.ToJSON(req), procVars))
utils.Logger.Err(fmt.Sprintf("<%s> no request processor enabled, ignoring request %s, agentRequest: %+v",
utils.RadiusAgent, utils.ToJSON(req), utils.ToJSON(agReq)))
return nil, nil
}
return
@@ -115,24 +116,21 @@ func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err e
// handleAcct handles RADIUS Accounting request
// supports: Acct-Status-Type = Start, Interim-Update, Stop
func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err error) {
req.SetAVPValues() // populate string values in AVPs
procVars := make(processorVars)
if avps := req.AttributesWithName("Acct-Status-Type", ""); len(avps) != 0 { // populate accounting type
switch avps[0].GetStringValue() { // first AVP found will give out the type of accounting
case RadAcctStart:
procVars[MetaRadReqType] = MetaRadAcctStart
case RadAcctInterimUpdate:
procVars[MetaRadReqType] = MetaRadAcctUpdate
case RadAcctStop:
procVars[MetaRadReqType] = MetaRadAcctStop
}
req.SetAVPValues() // populate string values in AVPs
dcdr, err := newRADataProvider(req) // dcdr will provide information from request
if err != nil {
utils.Logger.Warning(
fmt.Sprintf("<%s> error creating decoder: %s",
utils.RadiusAgent, err.Error()))
return
}
agReq := newAgentRequest(dcdr, ra.tenantCfg, ra.cgrCfg.DefaultTenant, ra.filterS)
rpl = req.Reply()
rpl.Code = radigo.AccountingResponse
var processed bool
for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors {
var lclProcessed bool
if lclProcessed, err = ra.processRequest(reqProcessor, req, procVars, rpl); lclProcessed {
if lclProcessed, err = ra.processRequest(reqProcessor, agReq, rpl); lclProcessed {
processed = lclProcessed
}
if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) {
@@ -140,12 +138,12 @@ func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err e
}
}
if err != nil {
utils.Logger.Err(fmt.Sprintf("<%s> error: <%s> ignoring request: %s, process vars: %+v",
utils.RadiusAgent, err.Error(), utils.ToJSON(req), procVars))
utils.Logger.Err(fmt.Sprintf("<%s> error: <%s> ignoring request: %s, agentRequest: %+v",
utils.RadiusAgent, err.Error(), utils.ToJSON(req), utils.ToJSON(agReq)))
return nil, nil
} else if !processed {
utils.Logger.Err(fmt.Sprintf("<%s> no request processor enabled, ignoring request %s, process vars: %+v",
utils.RadiusAgent, utils.ToJSON(req), procVars))
utils.Logger.Err(fmt.Sprintf("<%s> no request processor enabled, ignoring request %s, agentRequest: %+v",
utils.RadiusAgent, utils.ToJSON(req), utils.ToJSON(agReq)))
return nil, nil
}
return
@@ -178,7 +176,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
case utils.MetaDryRun:
utils.Logger.Info(
fmt.Sprintf("<%s> DRY_RUN, processorID: %s, CGREvent: %s",
utils.HTTPAgent, reqProcessor.Id, utils.ToJSON(cgrEv)))
utils.RadiusAgent, reqProcessor.Id, utils.ToJSON(cgrEv)))
case utils.MetaAuth:
authArgs := sessions.NewV1AuthorizeArgs(
reqProcessor.Flags.HasKey(utils.MetaAttributes),
@@ -191,7 +189,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
reqProcessor.Flags.HasKey(utils.MetaSuppliersEventCost),
*cgrEv)
var authReply sessions.V1AuthorizeReply
err = ha.sessionS.Call(utils.SessionSv1AuthorizeEvent,
err = ra.sessionS.Call(utils.SessionSv1AuthorizeEvent,
authArgs, &authReply)
if agReq.CGRReply, err = NewCGRReply(&authReply, err); err != nil {
return
@@ -204,7 +202,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
reqProcessor.Flags.HasKey(utils.MetaThresholds),
reqProcessor.Flags.HasKey(utils.MetaStats), *cgrEv)
var initReply sessions.V1InitSessionReply
err = ha.sessionS.Call(utils.SessionSv1InitiateSession,
err = ra.sessionS.Call(utils.SessionSv1InitiateSession,
initArgs, &initReply)
if agReq.CGRReply, err = NewCGRReply(&initReply, err); err != nil {
return
@@ -214,7 +212,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
reqProcessor.Flags.HasKey(utils.MetaAttributes),
reqProcessor.Flags.HasKey(utils.MetaAccounts), *cgrEv)
var updateReply sessions.V1UpdateSessionReply
err = ha.sessionS.Call(utils.SessionSv1UpdateSession,
err = ra.sessionS.Call(utils.SessionSv1UpdateSession,
updateArgs, &updateReply)
if agReq.CGRReply, err = NewCGRReply(&updateReply, err); err != nil {
return
@@ -226,7 +224,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
reqProcessor.Flags.HasKey(utils.MetaThresholds),
reqProcessor.Flags.HasKey(utils.MetaStats), *cgrEv)
var tRply string
err = ha.sessionS.Call(utils.SessionSv1TerminateSession,
err = ra.sessionS.Call(utils.SessionSv1TerminateSession,
terminateArgs, &tRply)
if agReq.CGRReply, err = NewCGRReply(nil, err); err != nil {
return
@@ -237,7 +235,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
reqProcessor.Flags.HasKey(utils.MetaAccounts),
reqProcessor.Flags.HasKey(utils.MetaAttributes), *cgrEv)
var eventRply sessions.V1ProcessEventReply
err = ha.sessionS.Call(utils.SessionSv1ProcessEvent,
err = ra.sessionS.Call(utils.SessionSv1ProcessEvent,
evArgs, &eventRply)
if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) {
cgrEv.Event[utils.Usage] = 0 // avoid further debits
@@ -251,7 +249,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
// separate request so we can capture the Terminate/Event also here
if reqProcessor.Flags.HasKey(utils.MetaCDRs) {
var rplyCDRs string
if err = ha.sessionS.Call(utils.SessionSv1ProcessCDR,
if err = ra.sessionS.Call(utils.SessionSv1ProcessCDR,
*cgrEv, &rplyCDRs); err != nil {
agReq.CGRReply.Set([]string{utils.Error}, err.Error(), false)
}
@@ -261,11 +259,13 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
} else {
agReq.Reply.Merge(nM)
}
//update rply *radigo.Packet
if err := radReplyAppendAttributes(rply, agReq, reqProcessor.ReplyFields); err != nil {
return false, err
}
if reqType == utils.MetaDryRun {
utils.Logger.Info(
fmt.Sprintf("<%s> DRY_RUN, HTTP reply: %s",
utils.HTTPAgent, utils.ToJSON(rply)))
fmt.Sprintf("<%s> DRY_RUN, Radius reply: %s",
utils.RadiusAgent, utils.ToJSON(rply)))
}
return true, nil
}

View File

@@ -191,7 +191,7 @@ func TestRAitAcctStart(t *testing.T) {
}
// Make sure the sessin is managed by SMG
var aSessions []*sessions.ActiveSession
if err := raRPC.Call("SMGenericV1.GetActiveSessions",
if err := raRPC.Call(utils.SessionSv1GetActiveSessions,
map[string]string{utils.RunID: utils.META_DEFAULT,
utils.OriginID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-51585361-75c2f57b"},
&aSessions); err != nil {
@@ -279,7 +279,7 @@ func TestRAitAcctStop(t *testing.T) {
t.Errorf("Unexpected CDR CostSource received for CDR: %v", cdrs[0])
}
if cdrs[0].Cost != 0.01 {
t.Errorf("Unexpected CDR Cost received for CDR: %v", cdrs[0])
t.Errorf("Unexpected CDR Cost received for CDR: %v", cdrs[0].Cost)
}
}
}

View File

@@ -324,7 +324,7 @@ func startRadiusAgent(internalSMGChan chan rpcclient.RpcClientConnection, exitCh
return
}
}
ra, err := agents.NewRadiusAgent(cfg, smgConn)
ra, err := agents.NewRadiusAgent(cfg, filterS, smgConn)
if err != nil {
utils.Logger.Err(fmt.Sprintf("<RadiusAgent> error: <%s>", err.Error()))
exitChan <- true

View File

@@ -29,18 +29,6 @@
"rals": {
"enabled": true,
"cdrstats_conns": [
{"address": "*internal"}
],
"pubsubs_conns": [
{"address": "*internal"}
],
"users_conns": [
{"address": "*internal"}
],
"aliases_conns": [
{"address": "*internal"}
],
},
"scheduler": {
@@ -52,26 +40,6 @@
"rals_conns": [
{"address": "*internal"}
],
"cdrstats_conns": [
{"address": "*internal"}
],
},
"cdrstats": {
"enabled": true,
},
"pubsubs": {
"enabled": true, // starts PubSub service: <true|false>.
},
"aliases": {
"enabled": true, // starts Aliases service: <true|false>.
},
"users": {
"enabled": true,
"indexes": ["SubscriberId"],
},
"resources": {
@@ -88,6 +56,18 @@
"sessions": {
"enabled": true,
"attributes_conns": [
{"address": "127.0.0.1:2012", "transport": "*json"}
],
"cdrs_conns": [
{"address": "127.0.0.1:2012", "transport": "*json"}
],
"rals_conns": [
{"address": "127.0.0.1:2012", "transport": "*json"}
],
"resources_conns": [
{"address": "127.0.0.1:2012", "transport": "*json"}
],
"debit_interval": "10s",
},
@@ -99,70 +79,73 @@
"request_processors": [
{
"id": "KamailioAuth",
"filters": ["*string:*request.request_type:*radAuth"],
"flags": ["*dryrun"],
"filters": ["*string:*vars.*radReqType:*radAuth"],
"flags": ["*auth", "*accounts",],
"continue_on_success": false,
"request_fields":[
{"tag": "RequestType", "field_id": "RequestType", "type": "*constant",
"value": "*prepaid", "mandatory": true},
{"tag": "OriginID", "field_id": "OriginID", "type": "*composed",
"value": "Acct-Session-Id;^-;Sip-From-Tag", "mandatory": true},
"value": "*request.Acct-Session-Id;^-;*request.Sip-From-Tag", "mandatory": true},
{"tag": "Account", "field_id": "Account", "type": "*composed",
"value": "User-Name", "mandatory": true},
"value": "*request.User-Name", "mandatory": true},
{"tag": "Destination", "field_id": "Destination", "type": "*composed",
"value": "Called-Station-Id", "mandatory": true},
"value": "*request.Called-Station-Id", "mandatory": true},
{"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed",
"value": "Event-Timestamp", "mandatory": true},
"value": "*request.Event-Timestamp", "mandatory": true},
{"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed",
"value": "Event-Timestamp", "mandatory": true},
"value": "*request.Event-Timestamp", "mandatory": true},
],
"reply_fields":[
{"tag": "MaxUsage", "field_id": "SIP-AVP", "type": "*composed",
"value": "^session_max_time#;*cgrReply>MaxUsage{*duration_seconds}", "mandatory": true},
"value": "^session_max_time#;*cgrReply.MaxUsage{*duration_seconds}", "mandatory": true},
],
},
{
"id": "KamailioAccountingStart",
"filters": ["*string:*request.request_type:*radAcctStart"],
"flags": ["*dryrun"],
"filters": ["*string:*request.Acct-Status-Type:Start"],
"flags": ["*initiate","*attributes","*resources","*accounts"],
"continue_on_success": false,
"request_fields":[
{"tag": "RequestType", "field_id": "RequestType", "type": "*constant",
"value": "*prepaid", "mandatory": true},
{"tag": "OriginID", "field_id": "OriginID", "type": "*composed",
"value": "Acct-Session-Id;^-;Sip-From-Tag;^-;Sip-To-Tag", "mandatory": true},
"value": "*request.Acct-Session-Id;^-;*request.Sip-From-Tag;^-;*request.Sip-To-Tag", "mandatory": true},
{"tag": "OriginHost", "field_id": "OriginHost", "type": "*composed",
"value": "NAS-IP-Address", "mandatory": true},
"value": "*request.NAS-IP-Address", "mandatory": true},
{"tag": "Account", "field_id": "Account", "type": "*composed",
"value": "User-Name", "mandatory": true},
"value": "*request.User-Name", "mandatory": true},
{"tag": "Destination", "field_id": "Destination", "type": "*composed",
"value": "Called-Station-Id", "mandatory": true},
"value": "*request.Called-Station-Id", "mandatory": true},
{"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed",
"value": "Ascend-User-Acct-Time", "mandatory": true},
"value": "*request.Ascend-User-Acct-Time", "mandatory": true},
{"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed",
"value": "Ascend-User-Acct-Time", "mandatory": true},
"value": "*request.Ascend-User-Acct-Time", "mandatory": true},
],
"reply_fields":[],
},
{
"id": "KamailioAccountingStop",
"filters": ["*string:*request.request_type:*radAcctStop"],
"flags": ["*dryrun"],
"filters": ["*string:*request.Acct-Status-Type:Stop"],
"flags": ["*terminate","*resources","*accounts","*cdrs"],
"continue_on_success": false,
"request_fields":[
{"tag": "RequestType", "field_id": "RequestType", "type": "*constant",
"value": "*prepaid", "mandatory": true},
{"tag": "OriginID", "field_id": "OriginID", "type": "*composed",
"value": "Acct-Session-Id;^-;Sip-From-Tag;^-;Sip-To-Tag", "mandatory": true},
"value": "*request.Acct-Session-Id;^-;*request.Sip-From-Tag;^-;*request.Sip-To-Tag", "mandatory": true},
{"tag": "OriginHost", "field_id": "OriginHost", "type": "*composed",
"value": "NAS-IP-Address", "mandatory": true},
"value": "*request.NAS-IP-Address", "mandatory": true},
{"tag": "Account", "field_id": "Account", "type": "*composed",
"value": "User-Name", "mandatory": true},
"value": "*request.User-Name", "mandatory": true},
{"tag": "Destination", "field_id": "Destination", "type": "*composed",
"value": "Called-Station-Id", "mandatory": true},
"value": "*request.Called-Station-Id", "mandatory": true},
{"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed",
"value": "Ascend-User-Acct-Time", "mandatory": true},
"value": "*request.Ascend-User-Acct-Time", "mandatory": true},
{"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed",
"value": "Ascend-User-Acct-Time", "mandatory": true},
{"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*usage_difference",
"value": "Event-Timestamp;^|;Ascend-User-Acct-Time", "mandatory": true},
"value": "*request.Ascend-User-Acct-Time", "mandatory": true},
{"tag": "Usage", "field_id": "Usage", "type": "*usage_difference",
"value": "*request.Event-Timestamp;*request.Ascend-User-Acct-Time", "mandatory": true},
],
"reply_fields":[],
},

View File

@@ -1902,6 +1902,7 @@ func (smg *SMGeneric) BiRPCv1TerminateSession(clnt rpcclient.RpcClientConnection
if !args.TerminateSession && !args.ReleaseResources {
return utils.NewErrMandatoryIeMissing("subsystems")
}
//
if args.CGREvent.Tenant == "" {
args.CGREvent.Tenant = smg.cgrCfg.DefaultTenant
}

View File

@@ -323,6 +323,7 @@ const (
TRIGGER_BALANCE_EXPIRED = "*balance_expired"
HIERARCHY_SEP = ">"
META_COMPOSED = "*composed"
META_USAGE_DIFFERENCE = "*usage_difference"
MetaString = "*string"
NegativePrefix = "!"
MatchStartPrefix = "^"
@@ -523,6 +524,7 @@ const (
MetaDryRun = "*dryrun"
Event = "Event"
EmptyString = ""
AgentRequest = "AgentRequest"
)
// Migrator Action