SessionS in CGrateS

In a scenario where we don’t know how long an event will be (for example at the start of a voice call, we don’t know how long it’s going to go for, or the start of a data session but we don’t know how much data will be used) but need to want to
A) charge for it and
B) apply some credit control to make sure the subscriber doesn’t consume more than their allowed balance
That’s when we start to use SessionS.

SessionS is what powers online charging, and it’s done with Unit Reservation, I’ve written about this in painful detail here.

For a voice call for example, we reserve talk time in advance, before the user actually consumes it, for example when the call starts, we reserve 30 seconds of credit from the user’s balance, then when the user has consumed this first 30 seconds of credit, we go back and request another 30 seconds of credit.
If there’s credit available, we grant it and the call is allowed to continue for another 30 seconds, and then the process repeats, until either the call ends or we go back for more credit and there’s none available, at which point we terminate the call.

Why is this important?
We may have multiple sources drawing down on an account at the same time, if you’re on a call while browsing, you’re doing two events that are charged, and may be charged from the same balance, and we don’t want to give you free calls or data just because you’re able to walk and chew gum at the same time.

CGrateS Agents such as Asterisk, Kamailio, FreeSWITCH, RADIUS and Diameter Agents handle most of the heavy lifting for us, but understanding how SessionS works for me at least, made working with these modules much easier.

So let’s set the scene, we’re going to create an Account with 10 units of *generic balance (I’m using generic as if we use time the numbers end up kinda big and it gets confusing to look at) and then consume over several transactions it until all the balance is gone

In the config we’ve disabled the debit_interval in session – Usually this is handled by the Agents, but for our demo we’re going to do it manually, so it’s off.

Let’s get setup, we’ll define a charger, and create an account and allocate some balance to it.

#Define default Charger
print(CGRateS_Obj.SendData({
    "method": "APIerSv1.SetChargerProfile",
    "params": [
        {
            "Tenant": "cgrates.org",
            "ID": "Charger_API_Default",
            "RunID": "*Charger_API_Default_RunID",  #Arbitrary Sting
            'FilterIDs': [],
            'AttributeIDs': ['*none'],
            'Weight': 999,
        }
    ]   }   ))  

#Add a balance to the account with type *generic with 10 units of balance
Create_Voice_Balance_JSON = {
    "method": "ApierV1.SetBalance",
    "params": [
        {
            "Tenant": "cgrates.org",
            "Account": "Nick_Test_123",
            "BalanceType": "*generic",
            "Categories": "*any",
            "Balance": {
                "ID": "10_units_generic_balance",
                "Value": "10",
                "Weight": 25,
                "Blocker": "true",       #This stops the Monetary Balance from being used
            }
        }
    ]
}
print(CGRateS_Obj.SendData(Create_Voice_Balance_JSON))

Alright, with that out of the way let’s start a session using SessionSv1.UpdateSession we’re going to define a CGrateS event to pass to it, and we’ll call it multiple times, but change the usage as we go.

To make our demo easier, I’ve nested a little for loop, so we can keep deducting balance,

now = datetime.datetime.now()
OriginID = str(uuid.uuid4())
call_event = {
                "RequestType": "*prepaid",
                "ToR": "*generic",
                "Tenant": "cgrates.org",
                "Account": "Nick_Test_123",
                "AnswerTime": "*now",
                "OriginID": str(uuid.uuid1()),
                "OriginHost": "ScratchPad",
            }

while input("Enter to continue or q to quit") != "q":
    call_event['Usage'] = str(input("Usage: "))
    result = CGRateS_Obj.SendData(
        {"method": "SessionSv1.UpdateSession", "params": [
            {
                "GetAttributes": False,
                "UpdateSession": True,
                "Subsystem" : "sessions",
                "Tenant": "cgrates.org",
                "ID": OriginID,
                "Context": None,
                "Time": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
                "Event":
                    call_event}
        ]})
    pprint.pprint(result)
print("Quit")

So now with this all in place, we define the default charger add add balance to an account (as the account doesn’t exist yet, this step creates the account too) in the first block of code, and this second block of code defines the event.

By running these together, we can start our session.

When you run it you’ll be prompted to press enter to continue or input q to quit, let’s enter to continue, then you’ll be asked for the usage, I’ve put 1 in the below example.

Enter to continue or q to quit
Usage: 1
Sending Request with Body:
{'method': 'SessionSv1.UpdateSession', 'params': [{'GetAttributes': False, 'UpdateSession': True, 'Subsystem': 'sessions', 'Tenant': 'cgrates.org', 'ID': '8e43c5e4-0b9b-4aaf-8d01-5143677d6a8a', 'Context': None, 'Time': '2024-06-14T22:22:52.279155Z', 'Event': {'RequestType': '*prepaid', 'ToR': '*generic', 'Tenant': 'cgrates.org', 'Account': 'Nick_Test_123', 'AnswerTime': '*now', 'OriginID': 'c86e7f54-2a48-11ef-9862-072e6d04df9b', 'OriginHost': 'ScratchPad', 'Usage': '1'}}]}
{'error': None, 'id': None, 'result': {'MaxUsage': 1}}

Alright, now let’s take a quick sidebar, and check in with cgr-console in a different tab, what do we think is going to show as our balance?

Well, if we run the accounts command from within cgr-console we can see our account which had a balance of 10 before, now has a balance of 9, as we’ve deducted 1 from the balance by inputting it as our usage:

And if we run the active_sessions command in the same console, we see the active sessions, where we can see where that one unit of balance went.

A few things to call out here:

  • The DebitInterval is how often this balance will be deducted, for our test scenario we’ve turned off automatic debiting, but Agents like FreeSWITCH and Kamailio leave this on and automatically tick off time as it passes (Oblivious this doesn’t work for data, so we’d leave it off)
  • The LoopIndex is how many UpdateSessions events the API has handled for this session (the unique session is identified by the ID / CGRID field
  • SetupTime is blank because we didn’t set it in our UpdateSession
  • The Usage in cgr-console is sometimes shown as nanoseconds, that’s because 1ns is equal to 1 generic unit.

So let’s go back to our Python script, go through the loop again but this time set the usage to 7.

Now if we flip back to cgr-console and check again, we’ll see, as expected that our account balance is now 2, and the active session has 8 of usage.

That’s because we started with 10, then we deducted 1, then we deducted 7, gives us 2 remaining. If we’re to run active_sessions again at cgr-console we’ll see the Usage of the session is now 8.

And lastly let’s try and take another 7 of balance, knowing we’ve only got 2 units left.

No dice, 7 is greater than 2 of course, so CGrateS stops us there, it’s done it’s job of making sure we didn’t allocate more of the credit than we were allowed and told us we have insufficient credit and that this balance is a blocker.

In this little demo we had one service drawing on the same source, but imagine if you’d fired up two copies of the script, you could have those two sources both consuming data at the same time, and this is where CGrateS shines; CGrateS can do all the heavy lifting to make sure that the resources are never over allocated, and that we’re not ending up with a negative balance.

When it comes time to terminating the session, there’s a trick to this.

Unit reservation is all about allocating resources in advance, this means we’ve generally have taken more money from the balance than we actually ended up consuming, so we have to give this back to the customer.

If we include the Usage field in the TerminateSession request, this must be the total usage for the entire session (start-to-finish), not just since the last UpdateSession API call.

For example if we allocated 30 seconds balance at the start of a call, then as that 30 seconds was consumed, we allocated another 30 seconds, and then when the call got 60 seconds in, we allocate another 30 seconds of balance. But if the call ends at a total of 70 seconds, we’ve allocated 90 seconds (3x 30 seconds), so we’d be over billing the customer. This is where we set Usage to 70 and CGrateS will refund the 20 seconds of balance we over charged them. This is because 3x 30 seconds = 90 seconds allocated, but the call only ended up using 70 seconds, so we need to refund 20 seconds of balance (90 – 70 = 20) to the Account balance.

That’s one way of doing it, but the other option is if we’ve just tracked usage since the last update, we have a 70 second call that we had allocated 3x 30 seconds Session Updates, we can set LastUsed to be 10 seconds (as we only used 10 seconds of the 30 seconds allocated in the last Update) which will also refund the 20 seconds.

In practice, you’ll probably use CGrateS Agents like the FreeSWITCH Agent, Asterisk Agent or Kamailio Agent to handle the charging in those applications. By using the premade CGrateS Agents, it handles generating the UpdateSession calls and all of this logic under the hood, but it’s super useful to know how it all works.

I’ve put the example cgrates.json file I used and the script for debiting on the Github repo for this post.

Leave a Reply

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