All Lua scripts executed by the N2 Logic Node, whether read from a file or from the compiled node source:
must contain the NCC handler function (and therefore include the N2 NCC API library), enabling the node to yield control back to the core ACS engine, allowing data and operations to flow into and out of the SLC’s runtime call control
may include any other libraries required
may include any other global definitions, such as variables or functions
In practical terms, every Lua script you want to use must follow this format:
local ncc = require "n2.ncc"
-- other included libraries
-- definitions
local logic = function (args)
-- logic goes here
end
return ncc.handler (logic)
Script Examples
A real, if trivial, example is the ubiquitous Hello World version for NCC:
local ncc = require "n2.ncc"
function hello ()
ncc.debug ("Hello, World")
end
local hw = function (a)
hello ()
end
return ncc.handler (hw)
A more detailed production-ready example would be a helper function to retrieve a specific balance type for the active subscriber and branch accordingly:
local ncc = require "n2.ncc"
local ccs = require "n2.ccs"
function getBalance (customer, balance)
local cust = 0
if type (customer) == type (1) then
cust = customer
else
cust = ncc.customer_id_for_name (customer)
if not cust then
if customer then
cust = customer
else
cust = "nil"
end
return "Invalid customer " .. cust, 0
end
end
local bal = 0
if type (balance) == type (1) then
bal = {['ID'] = balance} -- add to table for check
else
bal = ccs.customer.get_balance_type_by_name(customer, balance)
end
if not bal or not bal.ID then
if balance then
bal = balance
else
bal = "nil"
end
return "Invalid balance type " .. bal, 0
else
bal = bal.ID
end
local wi = ccs.wallet.info()
if not wi.BALANCES[bal] or not wi.BALANCES[bal].SYSTEM_VAL then
return 0, 0
else
local balAmt = wi.BALANCES[bal].SYSTEM_VAL
local balExp = walletUtils.earliestExpiry(customer, balance)
return balAmt, balExp
end
end
local f = function ()
local ba = 0
local be = 0
ba, be = getBalance ("Boss", "General Cash")
if type (ba) == type (1) then
-- store balance values in temporary storage for later control plan usage
ncc.profile.write_int ("TEMPORARY", 100, ba)
ncc.profile.write_int ("TEMPORARY", 101, be)
ncc.branch.exit (1)
else
-- error occurred
ncc.branch.exit (2)
end
end
return ncc.handler (f)
Loops
Be aware of Lua operations that are local to your script. An infinite loop will cause the call to be continually processed within the SLEE and (eventually) cause a core dump as slee_acs will not respond to a watchdog signal. Calling NCC API functions will prevent a core dump, but will not prevent an infinite loop. Use the while command carefully!
Script Processing
For operations that do not involve the NCC API, the Lua script will simply execute linearly and the node will take the Default branch (if not instructed otherwise and no error occurs).
When performing calls to NCC API functions, the script will yield control to the logic node runtime engine and wait to be instructed to begin again. Some NCC calls will return almost immediately (e.g. printing debug) while others will require the node to similarly yield control to the core NCC runtime and wait for a response (e.g. reading a profile tag value). There is no need to handle this yourself, however - simply construct your script as you would like to have it and the Lua and NCC runtimes will deal with the responsibility delegation themselves.
External Libraries
External Lua libraries - whether from other N2 API sources, your custom libraries, or other third-party extensions - can be included as required in any of your scripts. Simply include them at the top of the Lua chunk, e.g.:
local ncc = require "n2.ncc"
local pretty = require "pl.pretty"
local routine = function (args)
print (pretty.write (args))
ncc.debug ("Logic node processing started")
-- etc
The include path that the Lua interpreter uses is set in the LUA_PATH environment variable. This path should be used to contain any Lua chunks, both pre-compiled and as scripts.
For the ncc_oper operator, who runs the SLEE, this is stored in the following file:
Changes to either of these environment variables require a SLEE restart for the operator to pick up the modification.
I/O
In most cases, you will probably use the N2 NCC API to get information into or out of the node. However, you are by no means limited to this; all the features Lua offers for input and output are available to use.
As an example, you may want to diagnose problems with calls from a particular geographic region. In a normal NCC deployment, there is no way to do this easily, but with the Logic Node you can easily get information in real-time with minimal overhead and targeted just to the information you want:
local ncc = require "n2.ncc"
local h = function (a)
if (ncc.engine.field.MSC_ADDRESS.DIGITS == "12345"
and ncc.engine.location_num () == "54321") then
print ("Problem found! Let's get the language ID they think they're using ("
.. ncc.engine.language_id () .. ") and their time zone ("
.. ncc.engine.caller_logical_tz ())
end
end
return coroutine.create (h)
This script will write to the standard slee_acs log file every time just the targeted information required is found. This is independant of whether or not debug is enabled on the SLC, so can be used in production situations for real-time analysis and tracing.
Refreshing scripts
All scripts read from the file system will exhibit changes as soon as the script is updated. No caching is performed.
Configuration in the node, however, including script source if given, is subject to the standard ACS or CCS control plan caching and activation rules.
Error handling
The N2 Logic Node has been designed from a “fail quickly and loudly” perspective, assuming that the script author will want to know immediately when ‘bad’ values are passed to an API function. This enables your scripts to be stringently developed and rapidly debugged, with fewer places for subtle bugs to hide by the time it gets to your production network.
Most API functions will cause an error when provided input outside the expected ranges, e.g. an invalid profile block identifier or a non-existent engine field. This error will be alerted in the system log. For example:
Sep 22 17:00:00 n2-slc01 n2LogicNode: [ID 675952 user.error] n2LogicNode(12345) ERROR: {10006004} SDK: Thread error (/IN/service_packages/N2/lua/n2/ncc.lua:289: New engine field value must be supplied).
This matches the Lua approach, which will raise an error and stop script execution if it cannot fulfil the request - for example, indexing a non-table variable.
You should include adequate checking and exception handling in your scripts to avoid errors caused by unreliable inputs, e.g.:
local periodic_charges
local customers = ncc.customers ()
local cust_id = ncc.customer_id_for_name ('Test Service Provider')
if (customers and customers[cust_id]) then
periodic_charges = ccs.customer.periodic_charges (cust_id)
end
if (periodic_charges and type (periodic_charges) == 'table') then
-- iterate through table, etc.
Other resources
LuaDate v2, a Lua library for Gregorian date handling
Many, many examples and patterns of Lua usage can be found on the Lua Users Wiki
Core Scripting Errors
The N2 Logic Node framework may raise errors in various cases: if the script is not templated correctly, or if you yield control to the node outside the NCC API functions, or if it is not syntactically valid Lua, etc.
The Lua chunk has been loaded but is not able to be executed.
Resolution
Check that the Lua is syntactically valid. The error details provided should provide context.
Alarm
ERROR: {10006003}: Setup did not return a coroutine thread (call id: <SLEE call ID>,
script: <Lua script location>).
Problem
The Lua script was executed but did not return a coroutine thread for node control.
Resolution
Confirm that the return value in your script follows the script template and that the script is readable by the ncc_oper user.
Alarm
ERROR: {10006004}: Thread error (<Lua error>).
Problem
The thread ended abnormally (i.e. did not yield in a controlled manner).
Resolution
Confirm that the structure of your script follows the script template. The error details provided should provide context.
Alarm
ERROR: {10006005}: Invalid number of parameters (<number of parameters>)
returned from thread.
Problem
The thread yielded with an unexpected number of parameters. Exactly two parameters (in order) are expected in a thread response: an action (string) and a (possibly empty) table of arguments.
Resolution
Confirm that your script does not yield outside usage of NCC API functions. The error details provided should provide context.
Alarm
ERROR: {10006006}: Thread return parameter 1 (<parameter 1>) is not a string
indicating an action.
Problem
The thread yielded with an unexpected parameter in the action location. Exactly two parameters (in order) are expected in a thread response: an action (string) and a (possibly empty) table of arguments.
Resolution
Confirm that your script does not yield outside usage of NCC API functions.
Alarm
ERROR: {10006007}: Thread return parameter 2 (<parameter 2>) is not a table with
(possibly zero) parameters.
Problem
The thread yielded with an unexpected parameter in the action details location. Exactly two parameters (in order) are expected in a thread response: an action (string) and a (possibly empty) table of arguments.
Resolution
Confirm that your script does not yield outside usage of NCC API functions.
Alarm
ERROR: {10006008}: Unknown action (<action>) received from thread.
Problem
The thread yielded with an unrecognised action.
Resolution
Confirm that your script does not yield outside usage of NCC API functions.
Alarm
ERROR: {10006009}: Unknown engine field (<field ID>) received from thread.
Problem
The thread yielded with an engine field request but for an unrecognised enumerated field.
Resolution
Confirm that your script does not yield outside usage of NCC API functions.
Alarm
ERROR: {10006010}: Invalid field type (<Lua type>) for engine field access
received from thread.
Problem
The thread yielded with an engine field request but with a non-numeric type for the enumerated field.
Resolution
Confirm that your script does not yield outside usage of NCC API functions.
Alarm
ERROR: {10006011}: Invalid profile block type (<Lua type>) for profile access
received from thread.
Problem
The thread yielded with a profile request but with a non-numeric type for the profile block.
ERROR: {10006050}: Must have either both or neither of bucket and balance to cancel.
Problem
A wallet update request was received from the thread with only one of the bucket to cancel or balance to cancel specified.
Resolution
Ensure when sending a wallet update request you either specify both the bucket and balance cancellation details, or neither of the bucket and balance cancellation details.
Alarm
ERROR: {10006051}: Invalid cancel bucket ID type (<Lua type>) for wallet update
received from thread.
Problem
The thread yielded with a wallet update request but with a non-numeric type for the bucket ID to cancel.
Resolution
Ensure that when specifying the extra information for a bucket ID to cancel that you use a numeric, positive value.
Alarm
ERROR: {10006051}: Invalid cancel balance ID type (<Lua type>) for wallet update
received from thread.
Problem
The thread yielded with a wallet update request but with a non-numeric type for the balance ID to cancel.
Resolution
Ensure that when specifying the extra information for a balance ID to cancel that you use a numeric, positive value.
Alarm
ERROR: {10006053}: Invalid new wallet state () for wallet update
received from thread.
Problem
The thread yielded with a wallet update request but with an invalid new wallet state.
Resolution
Ensure that when specifying the wallet state for a wallet update that you use a valid state. Refer to the charging data model.
Alarm
ERROR: {10006054}: Invalid balance ID type (<Lua type>) for wallet update
received from thread.
Problem
The thread yielded with a wallet update request but with a non-numeric type for the balance ID.
Resolution
Ensure that when specifying the extra information for a balance ID that you use a numeric, positive value.
Alarm
ERROR: {10006055}: Invalid balance value type (<Lua type>) for wallet update
received from thread.
Problem
The thread yielded with a wallet update request but with a non-numeric type for the balance value.
Resolution
Ensure that when specifying the extra information for a balance value that you use a numeric, positive value.
Alarm
ERROR: {10006056}: Invalid extra information type (<Lua type>) for named event
received from thread.
Problem
The thread yielded with a named event request but with a non-string type for the extra information.
Resolution
Ensure that when specifying the extra information for a named event that you use a string value.
Alarm
ERROR: {10006057}: Invalid class type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-string type for the class.
Resolution
Ensure that when specifying the class of a named event that you use a string value.
Alarm
ERROR: {10006058}: Invalid name type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-string type for the name.
Resolution
Ensure that when specifying the name of a named event that you use a string value.
Alarm
ERROR: {10006059}: Invalid minimum units type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-numeric type for the minimum units required.
Resolution
Ensure that when specifying the minimum units required for a named event that you use a numeric value.
Alarm
ERROR: {10006060}: Invalid maximum units type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-numeric type for the maximum units required.
Resolution
Ensure that when specifying the maximum units required for a named event that you use a numeric value.
Alarm
ERROR: {10006061}: Invalid ignore balance limits flag type (<Lua type>) for
named event received from thread.
Problem
The thread yielded with a named event request but with a non-numeric type for the ignore balance limits flag.
Resolution
Ensure that when specifying the ignore balance units flag for a named event that you use a numeric value.
Alarm
ERROR: {10006062}: Invalid discount percentage type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-numeric type for the discount percentage.
Resolution
Ensure that when specifying the discount percentage for a named event that you use a numeric value.
Alarm
ERROR: {10006063}: Invalid caller timezone type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-string type for the caller timezone.
Resolution
Ensure that when specifying the caller timezone of a named event that you use a string value.
Alarm
ERROR: {10006064}: Invalid used units type (<Lua type>) for named event received from thread.
Problem
The thread yielded with a named event request but with a non-numeric type for the used units.
Resolution
Ensure that when specifying the used units for a named event that you use a numeric value.
Alarm
ERROR: {10006065}: Unexpected message received from direct named event request.
Problem
The ACS chassis returned an invalid message in response to a direct named event request.