Event handler scripts

An event handler script contains the logic that operates on the input JSON string passed to it by the event_mgr process each time a state change is detected for the paths monitored by the event handler instance.

Event handler scripts are executed by a MicroPython interpreter, and therefore have a limited set of modules available in the standard libraries compared to Python. You can use a regular Python interpreter to write the scripts for the event handler as long as you use standard libraries available for MicroPython.

Script input

Whenever a state change is detected for any of the monitored paths defined in the event handler instance, event handler calls the MicroPython script referenced in the event handler instance configuration. Event handler calls the event_handler_main() function in the script, passing it a JSON string indicating the current values of the monitored paths, as well as any other configured options.

Using the example in Event handler configuration, when the oper-state for the two links defined in paths changes to down, the event_mgr process generates the following JSON string and passes it to a script (named oper-group.py in the example) as input:

{
    "paths": [
        {
            "path": "interface ethernet-1/55 oper-state", 
            "value": "down"
        },
        {
            "path": "interface ethernet-1/56 oper-state",
            "value": "down"
        }
    ],
    "options": { 
        "required-num-up-links": "1",
        "down-links": [
            "ethernet-1/1",
            "ethernet-1/2",
        ]
    }
}

Script processing

The JSON string generated by the event handler is processed by a MicroPython script. In the following example, the script takes the state of the links in the supplied paths objects, and based on the required-num-up-links option:value pair, decides whether the links in the down-links option:value pair should be up or down. The script then generates a response consisting of actions for the SR Linux device to execute.

import sys
import json

# count_up_uplinks returns the number of monitored uplinks that have oper-state=up
def count_up_uplinks(paths):
    up_cnt = 0
    for path in paths:
        if path.get("value", "down") == "up":
            up_cnt = up_cnt + 1
    return up_cnt

# required_up_uplinks returns the value of the `required-up-uplinks` option
def required_up_uplinks(options):
    return int(options.get("required-up-uplinks", 1))

# main entry function for event handler
def event_handler_main(in_json_str):
    # parse input json string passed by event handler
    in_json = json.loads(in_json_str)
    paths = in_json["paths"]
    options = in_json["options"]
    num_up_uplinks = count_up_uplinks(paths)
    downlinks_new_state = (
        "down" if num_up_uplinks < required_up_uplinks(options) else "up"
    )

    # add `debug="true"` option to event-handler configuration to output parsed parameters
    if options.get("debug") == "true":
        print(
            f"num of required up uplinks = {required_up_uplinks(options)}\n\
detected num of up uplinks = {num_up_uplinks}\n\
downlinks new state = {downlinks_new_state}"
        )

    response_actions = []

    for downlink in options.get("down-links", []):
        response_actions.append(
            {
                "set-ephemeral-path": {
                    "path": "interface {downlink} oper-state",
                    "value": downlinks_new_state,
                }
            }
        )

    response = {"actions": response_actions}
    return json.dumps(response)

This script is an example of using event handler with the SR Linux operational groups feature. See Script processing for details about how each function of this script is processed.

Persistent-data object

The persistent-data object allows a script to maintain state between executions; for example, to count the number of times a particular action has been taken. The persistent-data object may be included in the JSON string returned by the script when invoked by event handler.

For example, if a script needs to count the number of times it has taken an action, it can return the following counter:

{
-- snip --
    "persistent-data": {
        "action-count": 5
    }
}

The next time event handler invokes the script, it includes the following as part of the input supplied to the script:

{
-- snip --
    "persistent-data": {
        "action-count": 5
    }
}

If configured to do so, the script can read this input and increment the counter, potentially returning the following:

{
-- snip --
    "persistent-data": {
        "action-count": 6
    }
}

Script output

The MicroPython script returns a single parameter, which is a JSON string with a structure expected by event handler. The script output contains a list of actions and persistent-data objects (if configured), which are passed to event_mgr for processing. See Actions for descriptions of the supported actions.

Using the example from Event handler configuration, if the script determines that the link in the down-links option must be brought down, it sends the following output JSON string to the Event Handler:

{
    "actions": [
        {
            "set-ephemeral-path": {
                "path": "/interface ethernet-1/1 oper-state",
                "value": "down",
            }
        }
    ]
}

Reloading the script

Whenever the configuration for an Event Handler instance changes, or the Event Handler instance is administratively disabled and re-enabled, any running calls to the previous main function are terminated, and the script is reloaded, losing any state information in the process. You can also reload the script manually using a tools command.

For example, the following command reloads the oper-group.py script:

--{ running }--[  ]--
# tools system event-handler instance oper-group.py reload

Actions

You can specify the following actions in a MicroPython script used with event handler:

The event_mgr process expects the structure of the output JSON string from the script to adhere to the following schema:

{
    "actions": [
        {
            "set-ephemeral-path": {
                "path": "",
                "value": "",
                "always-execute": false
            }
        },
        {
            "set-cfg-path": {
                "path": "",
                "json-value": {},
                "always-execute": false
            }
        },
        {
            "delete-cfg-path": {
                "path": "",
                "always-execute": false
            }
        },
        {
            "set-tools-path": {
                "path": "",
                "json-value": {},
                "always-execute": false
            }
        },
        {
            "run-script": {
                "cmdline": "",
                "always-execute": false
            }
        },
        {
            "reinvoke-with-delay": 5000
        }
    ],
    "persistent-data": {
        "last-state-up": false
    }
}

Actions are processed in the order they are defined above, in that set-ephemeral-path is processed first, and reinvoke-with-delay last. The only exceptions are for set-cfg-path and delete-cfg-path, which are processed in the order they are received.

set-ephemeral-path

Makes an ephemeral change to a state leaf.

This action can be used to ephemerally change the oper-state for a set of interfaces based on some criteria (for example, the number of uplinks in up state).

  • path is a leaf list in CLI notation that refers to the oper-state setting for a set of interfaces. Wildcards are not supported; ranges are supported.

  • value can be either down or up.

The following example changes the oper-state for a set of four interfaces to down:
{
    "actions": [
        {
            "set-ephemeral-path": {
                "path": "interface ethernet-1/{1..4} oper-state",
                "value": "down"
            }
        }
    ]
}

If more than one event handler instance sets oper-state for the same interface, actions changing the oper-state to down override actions changing the oper-state to up.

Note:

In the current release, only the interface oper-state is valid as a path used with set-ephemeral-path.

set-cfg-path

Makes a persistent change to the SR Linux configuration.

The change is applied to the running configuration, but it is not saved to the startup configuration unless an explicit save startup command is executed.

  • path is a leaf or leaf-list in CLI notation. Wildcards are not supported; ranges are supported

  • value is a configuration setting relevant to the path.

The following example uses the set-cfg-path action to configure the admin-state for an interface. The path contains the CLI command to set the admin-state, and the value contains the setting to apply to the interface.

{
    "actions": [
        {
            "set-cfg-path": {
                "path": "interface ethernet-1/3 admin-state",
                "value": "disable"
            }
        },
    ]
}

delete-cfg-path

Deletes a path in the SR Linux configuration.

The change is applied to the running configuration, but it is not saved to the startup configuration unless an explicit save startup command is executed.

  • path is a leaf or leaf-list in full, expanded CLI notation. No wildcards or ranges are supported. Deleted settings are reset to their default values where applicable.

The following example deletes the configuration for an interface:

{
    "actions": [
        {
            "delete-cfg-path": {
                "path": "interface ethernet-1/3"
            }
        }
    ]
}

set-tools-path

Executes an SR Linux tools command.

  • path is the CLI notation for a tools command (without the tools keyword). Wildcards are not supported; ranges are supported.

  • value specifies a required value for the tools command in the path. If the tools command does not require a value, the value must still be included, but left empty.

The following example uses the set-tools-path action to clear statistics for an interface. This action is equivalent to the tools interface ethernet-1/1 statistics clear CLI command.

{
    "actions": [
        {
            "set-tools-path": {
                "path": "interface ethernet-1/1 statistics clear",
                "value": ""
            }
        },
    ]
}

run-script

Issues a command to execute a script.

The command can chain into another Python or bash script, execute a binary, or execute a script in any language for which the system has an interpreter installed.

The script is executed as the user configured to execute event handler operations (see Configuring the user for event handler operations). If a user is not configured to execute event handler operations, they are executed by local SR Linux user admin. The working context of the script is the home directory of the user executing the script.

  • cmdline is the full command to execute the script, including any options. The command is executed in a bash shell. Specify the command as if it were entered at a shell prompt.

The following action executes a script:

{
    "actions": [
        {
            "run-script": {
                "cmdline": "/my-script.sh --arg1=val1 -t"
            }
        }
    ]
}

reinvoke-with-delay

Re-invokes the script following a specified delay time.

When the actions returned by a script include a value for reinvoke-with-delay, the script is automatically re-invoked following the number of milliseconds specified in the value. When the script is re-invoked, it uses the then-current value of all paths.

Format: reinvoke-with-delay : <delay-time>

<delay-time> can be from 1,000 - 300,000 milliseconds (5 minutes).

The following action causes the script to be re-invoked after 5,000 milliseconds:

{
    "actions": [
        {
            "reinvoke-with-delay": 5000
        }
    ]
}

always-execute

When set to true for an action, causes event handler to always process the action, regardless of whether the same action was processed previously.

By default, when a list of actions is processed, event handler checks the list against actions that have been processed previously. If an action is identical to one that was processed previously, event handler does not re-run the duplicate action. For example, if a script returns an action setting an interface oper-state to down, and a subsequent action sets the oper-state for the same interface to down, event handler ignores the duplicate action.

If you want to disable this default behavior and run the action regardless of whether the same action has been run previously, set always-execute for the action to true.

The following action runs a tools command that clears statistics for an interface. The always-execute setting for the action is true, so the command is run each time the action is received from a script.

{
    "actions": [
        {
            "set-tools-path": {
                "path": "interface ethernet-1/1 statistics clear",
                "value": "",
                "always-execute": true
        }
    ]
}

If always-execute is not set to true, then the interface statistics are cleared the first time the action is processed, but not if the same action is received again.