FilterS in CGrateS

FilterS do what it says in the name, they are a generic way to define filter rules that an event may or may not match.

Think of them as like a WHERE statement in SQL, they allow us to condition match.

So what would we use FilterS for? Well, let’s first checkout some example use cases:

We might want to provide 100 free minutes on Tuesdays, we know from this post on creating Balances in CGrateS how to create the balance, but we’d use FilterS to make sure the balance is only used on the Tuesday, by adding a filter to check for the day of the week to only match on a Tuesday.

We might define an Attribute to rewrite the Destination number into E.164, but we only want to apply that transformation if the number is in 0NSN format, we apply the translation with AttributeS but we would create a filter to match Destinations that match the given prefixes.

We might want to trigger a counter for calls where the duration of the call (Usage) is greater than 1 hour, we can do this with Thresholds to handle the counting and FilterS to only match if the call duration is greater than 1 hour.

A customer may have multiple DIDs / phone numbers they present as the From header, and we need a way to map phone number “99990001” through to “99990099” as the Account and change the Account to “Customer X”, we can do that with AttributeS to update the Account value in the request, and FilterS to control if that AttributeS rule is matched or not.

FilterS are used all over in CGrateS, if you’ve been following along, you’ve already come across FilterS in the FilterIDs fields in the API, which we’re going to look at using today.

There’s two ways to handle Filters inside CGrateS, they both act the same way but each have some pros and cons.

Inline Filters

The first option is an “inline” filter. Take for example this AttributeS rule using an inline Filter.

{
    "method": "APIerSv2.SetAttributeProfile",
    "params": [{
        "ID": "ATTR_Blog_Example_Inline",
        "Contexts": ["*any"],
        "FilterIDs": ["*string:~*req.Account:Nick"],
        "Attributes": [
            {
            "FilterIDs": [],
            "Path": "*req.ExamplePath",
            "Type": "*constant",
            "Value": "ExampleValue"
            }
        ],
        "Blocker": False,
        "Weight": 10
    }],
}

Let’s break down this filter,

"*string:~*req.Account:Nick"

A filter is made up of 3 components, the match “type”, the element to compare using the match and the values.

Match Types: The above example is matching based on it being a string (match type *string), but we can also match on prefixes, suffixes, destinations, empty, not equal to something, greater than, less than, timings and more.

Match Elements: Next up we’ve got the element, this is what part of a CGrateS event we’re matching with the Match Type we’ve selected. In the above example we’re matching for if the value is type *string and the Element is ~*req.Account. If you look at the requests (~*req.) in CGrateS, you can see the events, there’s all the standard fields like Account, Subject, Category, Tenant, Destination, etc, plus any custom ones you’re using, all of which we can use as an element to compare with our match type.

Match Values: Lastly we’ve got the conditions we’ll match on, in the example above it’s the string “Nick” – So what we’re checking is the match is *string and the element we’re getting the string from is ~*req.Account and if that matches the value “Nick” then ding-ding-ding- we’ve matched.

Obviously the values change based on what we’re doing, if we were prefix matching, we’d put the prefix to match in the value.

Value can also be a list, separated by the pipe (|) symbol for inline filters, so for example we could match “Nick” and also “Nicholas” (if I’m in trouble) with this inline filter:

"*string:~*req.Account:Nick|Nicholas"

Let’s look at a few more inline filters.

This filter will match any event where the Destination is one of ACMA’s fake phone number ranges:

"*prefix:~*req.Destination:6125550|6127010|6135550|6137010|6175550|6187010|6185550|6187010|61491570"

Each match element also has an inverse, for example, *prefix also has *notprefix for matching the reverse:

"*notprefix:~*req.Destination:6125550|6127010|6135550|6137010|6175550|6187010|6185550|6187010|61491570"

Let’s look at one more example, if the Usage is greater than 1 hour:

*gt:~*req.Usage:1h
Inline filter for any Australian E164 prefixes

FilterProfiles

Now we’ve covered the basics of creating Filters with the “Inline” method, let’s consider the limits of this.

If I had defined objects in AttributeS, ThresholdS, ResourceS, Balances and StatS to match when ~*req.Account is “Nick” using an inline filter, and then I change my name, I’d have to go to each of those elements and update them, and that’d be a pain (especially because I’d need to also change my domain name.)

Instead I can create a “Filter Profile” – A reference to a filter that I can reference from AttributeS, ThresholdS, ResourceS, Balances and StatS, and then I only need to update the Filter.

Let’s look at how that would look, first we’d create a new Filter Profile object using the API with:

{
    "method": "ApierV1.SetFilter",
    "params": [
        {
            "ID": "Filter_ACCOUNT_Nick",
            "Rules": [
                {
                    "Type": "*string",
                    "Element": "~*req.Account",
                    "Values": [
                        "Nick",
                        "Nicholas",
                    ]
                }
            ],
            "ActivationInterval": {}
        }
    ]
}

This is the same as the below inline Filter, like the inline filter it’ll match any time the ~*req.Account is a string that matches “Nick” or “Nicholas”

"*string:~*req.Account:Nick|Nicholas"

And then to update our AttributeS example from earlier, rather than defining the inline filter in the FilterIDs section, we just put the ID of the filter we created above:

{
    "method": "APIerSv2.SetAttributeProfile",
    "params": [{
        "ID": "ATTR_Blog_Example_Inline",
        "Contexts": ["*any"],
        "FilterIDs": ["Filter_ACCOUNT_Nick"],
        "Attributes": [
            {
            "FilterIDs": [],
            "Path": "*req.ExamplePath",
            "Type": "*constant",
            "Value": "ExampleValue"
            }
        ],
        "Blocker": False,
        "Weight": 10
    }],
}

Easy!

We saw in the example above that we could do Logical OR operations, if the Account is equal to “Nick” or “Nicholas”. But what one neat thing we can do with FilterProfiles is to do local AND Operations.

Let’s create a new FilterProfile called Filter_Sunday to match when the AnswerTime matches Timing named “Timing_Sunday”:

{
    "method": "ApierV1.SetFilter",
    "params": [
        {
            "ID": "Filter_Sunday",
            "Rules": [
                {
                    "Type": "*timings",
                    "Element": "~*req.AnswerTime",
                    "Values": ["Timing_Sunday"]
                }
            ],
        }
    ]
}

Now we can define an Attribute that will only match if the Account is equal to “Nick” or “Nicholas” AND the AnswerTime matches our “Timing_Sunday” timing profile:

{
    "method": "APIerSv2.SetAttributeProfile",
    "params": [{
        "ID": "ATTR_Blog_Example_Inline",
        "Contexts": ["*any"],
        "FilterIDs": [
             "Filter_ACCOUNT_Nick",
             "Filter_Sunday",
        ],
        "Attributes": [
            {
            "FilterIDs": [],
            "Path": "*req.FYI",
            "Type": "*constant",
            "Value": "Sunday_and_Nick_or_Nicholas"
            }
        ],
        "Blocker": False,
        "Weight": 10
    }],
}

So we can evaluate as AND by just putting both FilterProfiles in the FilterIDs field:

"FilterIDs": ["FLTR_X", "FLTR_Y"],

It’s up to you where you use Inline Filters vs Filter Profiles. As a general rule, if you don’t mind setting it on every object you’re touching, or you don’t reuse the Filter much, inline Filters is probably the way to go.
But if you use multiple subsystems and want to keep your logic more readable, perhaps use Filter Profiles – but again, there’s no hard rules.

Filter Profiles is something we’ve got fairly good coverage of in the CGrateS UI, but as far as I’m aware there’s not a simple “Test Filter” API endpoint, so generally I test out with AttributeS.

Leave a Reply

Your email address will not be published. Required fields are marked *