Scripting

Capabilities

Capability

Support

Comment

Back-pressure

Supported feature

See Backpressure and buffering

Read/update values

Supported feature

See Read and update Values

Logging

Supported feature

See Logging

Milestone trigger event

Supported feature

See Milestone

Importing script

Supported feature

See Import scripts

Scheduling executions

Supported feature

See Scheduled executions

Interact with alarms

Supported feature

See Alarms manipulation

Interact with collections

Supported feature

See Collections

Validate data with filters

Supported feature

See Alarm or collection filter

Sending notification (SMS/EMAIL)

Supported feature

See Communications

Get the list of users

Supported feature

See Keycloak users

Format JS dates

Supported feature

See Date converter and formatter

Generate random UUID

Supported feature

See Generate UUIDv4 ids

Sending SNMP TRAP

Supported feature

See Sending SNMP TRAP

Replaying SNMP TRAP

Supported feature

See snmp.replayTrap()

HTTP request

Supported feature

See HTTP requests

Concepts

Overview

The osp-scripts plugin provides the ability to execute scripts written in Javascript and to interact with hierarchy elements and external systems.

Behavior

The osp-scripts provides a Javascript engine based on GraalVM, on which scripts are run on transient contexts, meaning that they do not have persistent state on their own (setting a value to a variable defined in the global scope of the script will be forgotten in the next script execution).

Scripts come in two forms :

  • Value scripts : Those scripts are bound to a value in the hierarchy and their result is used to generate the value content. They provide a way of computing a complex state and provide it to the hierarchy.

  • Detached scripts : Those scripts are detached from the hierarchy. They provide a way to execute actions on the hierarchy or on external systems.

Scripts executions follow the following constraints :

  • A script is guaranteed to have the view of the hierarchy as it was when it was triggered, independently of potential execution delays

  • If a value triggers the execution of multiple scripts, all scripts are guaranteed to have the same hierarchy view

  • Executions of different scripts are always run in parallel

  • Executions of the same value script are always run sequentially, for coherent update of their value

  • Executions of the same detached script are always run in parallel

Script execution

The script execution use a shared pool of threads, this pool must fit the need of the current installation with a tradeoff between :

  • Memory usage

  • Context switching

  • I/O duration

Warning

In general, the number of threads should be increased in case of a high number of scripts running at the same time. The more scripts are idle (like waiting on an external system), the more threads can be increased to increase throughput. But there is a drawback, each thread consumes memory and in case of too many context switches, the overall performance of the system can be affected.

Concurrency

A value script (describe in owner.scripts) is always executed in a concurrent way to avoid creating some edge cases on the impacted value.

A detached script can be concurrent so it’s must be taken into account that all interactions of the scripts on external system can arrive in any order :

For example a simple script logging in into a system and then performing a task can fail in concurrent way, if multiple login is not supported :

The intended script execution

scriptExecution1 -> WebSite : Login
WebSite -> scriptExecution1 : 200, ok
scriptExecution1 -> WebSite : doSomeAction
WebSite -> scriptExecution1 : 200, ok

The executed script in concurrency

scriptExecution1 -> WebSite : Login
WebSite -> scriptExecution1 : 200, ok
scriptExecution2 -> WebSite : Login
WebSite -> scriptExecution2 : 200, ok
WebSite -> scriptExecution1 : Connection closed (new connection)
scriptExecution1 -> WebSite : doAction
WebSite -> scriptExecution1 : 401, unauthenticated
scriptExecution1 -> WebSite : doSomeAction

Backpressure and buffering

When a script (value or detached) is called, it’s executed at most X times depending of the concurrency value set.

If the script execution is slower than the execution schedule frequency, the system will bufferize the newly added scripts executions.

When the buffer is full (each value has his own buffer), the system will use one of the backpressure strategy :

  • drop_older (drop the old values)

  • drop_latest (drop the new values)

Trigger information

Scripts can access the reason that triggered their execution by global variable trigger (trigger.id, trigger.content, …).

A trigger has the following structure :

  • id : the id of the value (string)

  • name : the name of the value (string)

  • description : the description of the value (string)

  • content (nullable) : the content of the value, can be null in case of reading errors (boolean, number or string depending on the type field)

  • error : the error in case of error, empty otherwise (string)

  • type : BOOLEAN, INTEGER, DECIMAL, TEXT (string)

  • timestamp : the timestamp attached to the value (integer)

Note

For detached scripts executed manually (i.e., not from callbacks, like an explicit call to scripts.run()), trigger only provides following fields :

  • id : always contains internal.manual, useful to determine if script was triggered manually or by a callback (string)

  • parameters : parameters list that were provided to the script (string[])

Warning

Owner script can only be triggered by callbacks. Manual actions like Actions can’t trigger an owner script.

Import scripts

Inside your scripts you may need some additional modules or functions. This section explain how to import them in your configuration. Here are some restrictions:

  • You need the source code you want to include (no package importation).

  • Only scripts with .mjs file extension can make use of the import keyword (it will be ignored if the extension is simply .js).

  • Objects or functions you want to import have to be marked with the export keyword.

Note

If you have a script with .js extension, you can freely rename it to .mjs.

A script can be imported with the following step:

  1. Add you script in your configuration directory (we will call it <script_path>).

  2. Add an entry in the file : modules/scripts/<module_script>/module.resources:

"resources": [
    {
        "source": "<script_path>",
        "destination": "<script_path>"
    }
]
  1. Add an import statement in your script:

import { my_function } from "<script_path>";

Note

The path to your script can be relative or absolute.

You may want to share a lib through all your configuration and for every script. The previous method is not very friendly and may result to an irrelevant file structure. For every library that may be shared between multiple scripts, the best practice is to place all of them outside the root folder :

  1. Create a lib directory at the root of your project and put every scripts that you want to be global inside it :

<pre>
...
├── certs
├── keys
├── lib
│   └── my_lib.mjs <-- this file
├── modules
├── offline-license-request
├── root
├── schema
├── stack
...
</pre>
  1. Then place every scripts that you need to be global inside the lib directory. Now we need to edit the modules/scripts/<module_script>/module.resources file and link the folder lib at in the root folder. Here is an example :

{
    "resources": [
        {
        "source": "lib/",
        "destination": "root/"
        }
    ]
}
  1. Finally, we can access to our script using the import statement like this:

import { my_function } from "my_lib.mjs";

Warning

When migrating a script from/to .mjs, be careful to check your script. The JavaScript flavor used by the *.mjs is ECMAScript 6.

Scheduled executions

Scripts executions can be scheduled periodically using cron expressions. The precision of those expressions is at the second, for example * * * * * ? * will schedule a script every second.

Schedule execution is setup in either owner.scripts or detached.scripts with scheduledExecutions key.

There is some good resources online for helping you generate cron entries Quartz generator help