Scripting
Capabilities
Capability |
Support |
Comment |
---|---|---|
Back-pressure |
||
Read/update values |
||
Logging |
See Logging |
|
Milestone trigger event |
See Milestone |
|
Importing script |
See Import scripts |
|
Scheduling executions |
||
Interact with alarms |
||
Interact with collections |
See Collections |
|
Validate data with filters |
||
Sending notification (SMS/EMAIL) |
See Communications |
|
Get the list of users |
See Keycloak users |
|
Format JS dates |
||
Generate random UUID |
||
Sending SNMP TRAP |
||
Replaying SNMP TRAP |
||
HTTP request |
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
The executed script in concurrency
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:
Add you script in your configuration directory (we will call it
<script_path>
).Add an entry in the file :
modules/scripts/<module_script>/module.resources
:
"resources": [ { "source": "<script_path>", "destination": "<script_path>" } ]
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 :
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>
Then place every scripts that you need to be global inside the
lib
directory. Now we need to edit themodules/scripts/<module_script>/module.resources
file and link the folderlib
at in theroot
folder. Here is an example :
{ "resources": [ { "source": "lib/", "destination": "root/" } ] }
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