QuantaStor Custom Scripting / Application Extensions: Difference between revisions

From OSNEXUS Online Documentation Site
Jump to navigation Jump to search
m Reword the directory-permissions note: a QuantaStor appliance has a single sudo-capable account, so the previous 'any local account can plant a script and have it run as root' overstated the risk. Keeps the mode, the consequence that call-outs run as root, the hardening instruction and the QSTOR-12443 reference
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
[[Category:admin_guide]]
[[Category:admin_guide]]


== Custom Scripting / Application Extensions ==
QuantaStor has script call-outs which you can use to extend the functionality of the system for integration with custom applications. A call-out is a shell script you place at a fixed path on the appliance; QuantaStor runs it as root at a defined point in a storage operation -- before a pool starts, after a share is created, around a snapshot schedule, either side of an HA failover -- and passes it the identity of the object being operated on.


QuantaStor has script call-outs which you can use to extend the functionality of the system for integration with custom applications.  For example, you may have an application which needs to be notified before or after a storage pool starts or stops.  Or you may have need to call a script before an automated snapshot policy starts in order to quiesce applications.
Because these scripts run as root and are triggered by ordinary storage activity, read [[#Securing the call-out directory|Securing the call-out directory]] before you install your first one.


''Note: The QuantaStor process that runs these custom scripts has a timeout value of 300 Seconds to ensure that the process can continue and not pause for an unexpected amount of time. If your scripts when implemented take longer than 300 seconds, please review them and take steps to reduce their execution time.''
{| class="wikitable"
! Section !! Purpose
|-
| [[#How call-outs work|How call-outs work]] || Naming, synchronous versus asynchronous, the environment scripts run in, what happens to a non-zero exit
|-
| [[#Securing the call-out directory|Securing the call-out directory]] || Ownership and permissions, and why a writable call-out directory is a root escalation path
|-
| [[#Storage System call-outs|Storage System call-outs]] || Service start, chassis beacon, system use notification
|-
| [[#Network port call-outs|Network port call-outs]] || Either side of bringing an interface up
|-
| [[#Storage Pool call-outs|Storage Pool call-outs]] || Start, stop, export, HA failover, scrub
|-
| [[#Network Share call-outs|Network Share call-outs]] || Share create and modify
|-
| [[#Snapshot and replication schedule call-outs|Snapshot and replication schedule call-outs]] || Quiesce and thaw around scheduled snapshots and replication
|-
| [[#DR failover call-outs|DR failover call-outs]] || Either side of Activate Checkpoints
|-
| [[#Software adapter call-outs|Software adapter call-outs]] || Either side of iSCSI and NVMe-oF logins
|-
| [[#Other extension points|Other extension points]] || Backup policy post-job handler, alternate pool import script, alert handlers
|-
| [[#Calling the QuantaStor API from a call-out|Calling the QuantaStor API from a call-out]] || Getting object detail back out of QuantaStor from inside your script
|-
| [[#Example script|Example script]] || A starting-point script that parses the arguments
|-
| [[#Troubleshooting a call-out that is not firing|Troubleshooting a call-out that is not firing]] || The log to read and the three things that are usually wrong
|-
| [[#Call-outs and upgrades|Call-outs and upgrades]] || What survives a package upgrade and the one name that does not
|}


=== Security Issues ===
== How call-outs work ==


Scripts are called from the root user account so it's important to use caution when adding custom call-out scripts to QuantaStor systems.  All call-out scripts must be installed to the ''/var/opt/osnexus/custom'' directory. By default the scripts directory has permissions '755'.  Scripts must be configured with file permissions using the command 'chmod 755 scriptname.sh' to prevent non-root user accounts from modifying the scripts.  Additionally, if you have sensitive information like a plain text password in your custom script be sure to set the permissions to 700 rather than 755 so only the root user account can read the script.
=== Where scripts go, and what they are called ===


=== Timeouts ===
Every call-out lives in one directory and its file name is fixed. QuantaStor looks for the exact name; there is no registration step and no configuration file.


Scripts must complete within 5 minutes; scripts taking longer are automatically terminated unless the script has the -async suffix.  For example:
<pre style="font-size: smaller">
 
* this version runs synchronously, must complete in 5 minutes: /var/opt/osnexus/custom/system-poststart.sh
* this version runs async, can run for as long as required: /var/opt/osnexus/custom/system-poststart-async.sh
 
=== Where to install custom scripts ===
 
Custom script call-outs are hard-wired to specific file names and must be placed in the custom scripts
directory '/var/opt/osnexus/custom' within your QuantaStor System .  If you have a grid of
systems you'll need to install your script onto all of the systems.
 
Custom Scripts Directory:
<pre>
/var/opt/osnexus/custom
/var/opt/osnexus/custom
</pre>
</pre>


== Storage System Custom Scripts ==
Each call-out has two possible forms, and the suffix decides how it runs:


Scripts related to the startup / shutdown of the system.
{| class="wikitable"
! Form !! How it runs
|-
| {{Code|1=&lt;hook&gt;.sh}} || '''Synchronously.''' The operation that triggered it waits for the script to finish. Wrapped in {{Code|1=/usr/bin/timeout -k 15s 5m}}, so the script gets SIGTERM after five minutes and SIGKILL fifteen seconds after that.
|-
| {{Code|1=&lt;hook&gt;-async.sh}} || '''In the background.''' The operation does not wait, there is no timeout, and the script can outlive the operation entirely.
|}


==== /var/opt/osnexus/custom/system-poststart.sh ====
If both forms exist QuantaStor runs both, starting the asynchronous one first and then the synchronous one. Use the asynchronous form for anything slow, and the synchronous form only when the work genuinely has to complete before the operation continues.


The system poststart script is only called one time when the system boots up. If the management services are
A synchronous script blocks for its full runtime. A {{Code|1=poolscrub-prestart.sh}} that sleeps for 25 seconds makes {{Code|1=[[QuantaStor CLI Command Reference#pool-scrub-start|qs pool-scrub-start]]}} take 27 seconds instead of about one. Under HA failover and pool start, that delay is added to your recovery time, so keep synchronous scripts short.
restarted it will check against the timestamp in /var/opt/osnexus/quantastor/qs_lastreboot an only call the
system-poststart.sh script if it has changed. If you want your poststart script to run every time the
management service is restarted you can just delete the qs_lastreboot file in your script.


==== /var/opt/osnexus/custom/system-prestop.sh ====
=== The environment scripts run in ===


Called when the user initiates a shutdown or a restart via the web management interface (or CLI).  Note that
Call-outs are executed by the QuantaStor service, so they inherit its environment and not a login shell's:
if the admin bypasses the normal shutdown procedure and restarts the system at the console
using 'reboot' or 'shutdown -P now' or similar command your script won't get called.


==== /var/opt/osnexus/custom/system-ident-start.sh ====
{| class="wikitable"
! !! Value
|-
| User || {{Code|1=root}} (uid 0), always
|-
| Working directory || {{Code|1=/opt/osnexus/quantastor/bin}}
|-
| {{Code|1=PATH}} || {{Code|1=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin}}
|-
| {{Code|1=HOME}}, {{Code|1=USER}} || empty
|-
| {{Code|1=TERM}} || {{Code|1=dumb}}
|-
| {{Code|1=LANG}} || {{Code|1=C.UTF-8}}
|}


Called before the Storage System Beep task is called to identify the chassis of the storage system via audible beep and/or LED blink on the storage server. There are no input args to this script.
There are only nine environment variables in total and none of your shell profile is loaded, so use absolute paths for every command and file your script touches, and do not rely on {{Code|1=$HOME}} or on anything a login would have set. Note in particular that {{Code|1=$HOME}} is an empty string, which breaks any tool configured through a path built from it.


==== /var/opt/osnexus/custom/system-ident-stop.sh ====
=== Exit codes are logged, not acted on ===


Called after the Storage System Beep task is complete. There are no input args to this script.
A non-zero exit from a synchronous call-out is recorded as a warning together with the script's standard error, and '''the operation continues regardless'''. A call-out cannot veto a pool start, a failover or a snapshot. A script that exits 42 during a pool scrub start produces this and the scrub proceeds:


==== /var/opt/osnexus/custom/security-message-update.sh ====
<pre style="font-size: smaller">
WARN custom_script_manager  <the script's stderr, line by line>
WARN custom_script_manager  Custom script '/var/opt/osnexus/custom/poolscrub-prestart.sh' completed with error '42'.
</pre>


Called with argument --message="SYSTEMUSE" where SYSTEMUSE is the system use notification message set in the Security Manager dialog. This enables one to set the system wide system use / security-classification message in other areas such as in 99-class-banner for gnome-shell extensions.
The practical consequence is that a broken call-out is invisible from the web interface and from the task list. Nothing turns red. The only place it shows up is the service log -- see [[#Troubleshooting a call-out that is not firing|Troubleshooting a call-out that is not firing]].


== Network Interface Custom Scripts ==
=== Grid systems ===


Scripts related to the starting of network interfaces/ports.  
A call-out runs on the node that is performing the operation, using that node's copy of the script. In a grid, install your scripts on '''every''' node that could own the object, or the call-out will fire on some failovers and pool moves and not others.


==== /var/opt/osnexus/custom/port-prestart.sh ====
=== Turning call-outs off ===


Called before the network port is started (eg ifup) with the --name= argument providing the port name.
Call-outs are on whenever the directory exists. To disable the mechanism entirely -- for a support investigation, say -- the QuantaStor service accepts a {{Code|1=disable-custom-scripts-manager}} startup argument. We recommend contacting support before changing service startup arguments.


==== /var/opt/osnexus/custom/port-poststart.sh ====
== Securing the call-out directory ==


Called after the network port is started (eg ifup) with the --name= argument providing the port name.
'''The call-out directory is a root execution path.''' Anything in it named after a hook is run as root the next time an ordinary, unprivileged storage event occurs -- a pool starting, a network port coming up, a snapshot schedule triggering, a share being created. Write access to that directory is therefore equivalent to root on the appliance, and it needs to be treated the way you would treat {{Code|1=/etc/sudoers.d}}.


== Storage Pool HA Failover Custom Scripts ==
'''Check the directory permissions before you install anything.''' Current releases ship {{Code|1=/var/opt/osnexus/custom}} '''world-writable (mode 0777)''' on a fresh install, which is more permissive than intended -- scripts placed there are modifiable by any account on the system, and call-outs run as {{Code|1=root}}. This is a packaging defect, tracked as QSTOR-12443. Repair it, and repair the sibling directories that have the same problem:


These scripts make it possible to add custom scripting to the HA failover process for ZFS based storage pools.
<pre style="font-size: smaller">
chown root:root /var/log/qs /var/opt/osnexus/custom /var/opt/osnexus/alerthandlers /var/opt/osnexus/quantastor
chmod 755 /var/log/qs /var/opt/osnexus/custom /var/opt/osnexus/alerthandlers /var/opt/osnexus/quantastor
</pre>


Command Arguments:
Then set each script to mode 755 and root ownership, so that no non-root account can modify a script that root is going to execute:


'''--pool=POOLUUID'''
<pre style="font-size: smaller">
chown root:root /var/opt/osnexus/custom/*.sh
chmod 755 /var/opt/osnexus/custom/*.sh
</pre>


==== /var/opt/osnexus/custom/pool-prefailover.sh ====
If a script contains sensitive information -- a plain text password, an API token, a private key path -- use '''700''' rather than 755 so only root can read it. The service runs as root, so 700 does not stop the call-out from working. Better still, keep the secret in a separate root-only file and read it from the script.


This is called after the before the failover starts --pool arg containing the UUID of the pool which is being operated on.
Two further points worth making:


==== /var/opt/osnexus/custom/pool-postfailover.sh ====
* '''Hardening survives an upgrade.''' A directory mode you set by hand is preserved across a package upgrade, so this is a one-time repair per appliance rather than something to redo each release.
* '''Anyone who can write a call-out owns the appliance.''' That includes any service account with write access to the directory and any process running as a non-root user that can reach it. If you are auditing privilege escalation paths on a QuantaStor system, this directory belongs on the list along with the alert handler directory.


This is called after the failover completes (successfully or not) with the --pool arg containing the UUID of the pool which is being operated on.
See [[Security Configuration]] for the appliance's wider security controls.


== Storage Pool Custom Scripts ==
== Storage System call-outs ==


If you have custom applications running within the system which need to attach/detach from
{| class="wikitable"
the pool or specific directories within a given storage pool these scripts may be helpful to you.
! Script !! Fires !! Arguments
|-
| {{Code|1=system-poststart.sh}} || Every time the QuantaStor service finishes starting || {{Code|1=--firstboot}}, only when the appliance itself has rebooted since the previous start; otherwise no arguments
|-
| {{Code|1=system-ident-start.sh}} || Before the Storage System beacon task begins its LED blink and audible beep pattern || none
|-
| {{Code|1=system-ident-stop.sh}} || After the beacon task completes || none
|-
| {{Code|1=security-message-update.sh}} || When a non-empty System Use Notification is applied || {{Code|1=--message="TEXT"}}
|}


Command Arguments:
=== system-poststart.sh ===


'''--pool=POOLUUID'''
This script runs on '''every''' service start, including a plain service restart with no reboot. What changes between the two cases is the argument: QuantaStor compares the current boot time against the stamp it keeps at {{Code|1=/var/opt/osnexus/quantastor/qs_lastreboot}} and passes {{Code|1=--firstboot}} only when the appliance has actually rebooted. Branch on that argument if you want work that happens once per boot rather than once per service start.


==== /var/opt/osnexus/custom/pool-poststart.sh ====
{{Code|1=system-poststart-async.sh}} is '''reserved by the product''' and is not available to use. QuantaStor installs its own Ceph OSD activation script at that name as a symlink, and the package replaces whatever is there on every install and upgrade, silently. Use the synchronous {{Code|1=system-poststart.sh}} instead, or start your own background work from it. This collision is tracked as QSTOR-12444.


Called just after a storage pool is started.  The UUID of the pool is provided as an input arguement to the
=== system-prestop.sh ===
script as '--pool=<POOLUUID>'.  You can use 'qs pool-get <POOLUUID> --server=localhost,admin,password --xml' to get more detail about the storage pool from
within your script.  The --xml flag is optional, and you'll need to provide the correct admin password.


==== /var/opt/osnexus/custom/pool-prestop.sh ====
'''This call-out does not currently fire.''' The hook is implemented in the service but nothing invokes it, so a script installed at this name never runs on a shutdown or restart, whether initiated from the web interface, the CLI or the console. Tracked as QSTOR-12445. If you need work done before a controlled shutdown, drive it from your own orchestration rather than from this call-out.


Called just before the pool is stopped.
=== security-message-update.sh ===


==== /var/opt/osnexus/custom/pool-preexport.sh ====
Called with the System Use Notification text configured under [[Security Configuration]], so you can mirror the same banner into other subsystems -- a GNOME classification banner, for example, or a login screen you manage yourself. The text is sanitized before it reaches your script: double quotes become single quotes, backslashes become forward slashes, and non-ASCII characters are removed. The call-out is not run when the notification is cleared; QuantaStor removes {{Code|1=/etc/issue.notice}} instead.


Called before a pool is exported.
== Network port call-outs ==


==== /var/opt/osnexus/custom/pool-postexport.sh ====
{| class="wikitable"
! Script !! Fires !! Arguments
|-
| {{Code|1=port-prestart.sh}} || Immediately before QuantaStor brings a port up || {{Code|1=--name=PORTNAME}}
|-
| {{Code|1=port-poststart.sh}} || After the port is up and its configuration has been reconciled || {{Code|1=--name=PORTNAME}}
|}


Called just after a storage pool is exported.
{{Code|1=port-prestart.sh}} can fire more than once for a single port activation. When the port is a bond, QuantaStor cycles the slave interfaces first and calls the script around that step as well as around the bond itself. Write the script so that running it twice in a row is harmless.


== Storage Pool Scrub / Bit-rot Detection & Correction Scripts ==
== Storage Pool call-outs ==


==== /var/opt/osnexus/custom/poolscrub-prestart.sh ====
These are the call-outs to use when an application on the appliance has to attach to or detach from a pool, or from a directory inside one, in step with the pool's lifecycle. All of them take the pool's UUID.


Called just before a storage pool scrub is started.  Arguments are --name= and --id= which have the Storage Pool name and UUID respectively.
{| class="wikitable"
! Script !! Fires !! Arguments
|-
| {{Code|1=pool-poststart.sh}} || After a pool has been started || {{Code|1=--pool=POOLUUID}}
|-
| {{Code|1=pool-prestop.sh}} || Before a pool is stopped || {{Code|1=--pool=POOLUUID}}
|-
| {{Code|1=pool-preexport.sh}} || Before a pool export begins || {{Code|1=--pool=POOLUUID}}
|-
| {{Code|1=pool-postexport.sh}} || After the export finishes, whether or not it succeeded || {{Code|1=--pool=POOLUUID}}
|-
| {{Code|1=pool-prefailover.sh}} || Before an HA failover of the pool starts || {{Code|1=--pool=POOLUUID}}
|-
| {{Code|1=pool-postfailover.sh}} || After the failover completes, whether or not it succeeded || {{Code|1=--pool=POOLUUID}}
|-
| {{Code|1=poolscrub-prestart.sh}} || Before a pool scrub is started || {{Code|1=--name=POOLNAME --id=POOLUUID}}
|-
| {{Code|1=poolscrub-poststart.sh}} || Just after the scrub has been started || {{Code|1=--name=POOLNAME --id=POOLUUID}}
|}


==== /var/opt/osnexus/custom/poolscrub-poststart.sh ====
Points worth knowing:


Called just after a storage pool scrub is started. Arguments are --name= and --id= which have the Storage Pool name and UUID respectively.
* '''The post script always runs.''' Export and failover are paired so that the {{Code|1=post}} call-out fires even when the operation throws partway through. Write {{Code|1=pool-postexport.sh}} and {{Code|1=pool-postfailover.sh}} as cleanup that must be safe on both the success and failure paths, and do not treat being called as proof the operation worked -- query the pool state if you need to know.
* '''{{Code|1=poolscrub-poststart.sh}} fires when the scrub has been ''started'', not finished.''' A scrub runs for hours; the call-out returns as soon as the scrub is under way. There is no scrub-completion call-out.
* '''{{Code|1=pool-poststart.sh}} also covers CephFS pools''', firing when the pool is mounted on its owning node. {{Code|1=pool-prestop.sh}} fires for every pool stop regardless of pool type. The scrub call-outs are ZFS-only, because a scrub request against a CephFS pool is rejected before they are reached -- Ceph scrubbing is driven by policy on the underlying OSDs instead.
* Keep {{Code|1=pool-prefailover.sh}} and {{Code|1=pool-poststart.sh}} fast if they are synchronous. Both sit directly in the HA recovery path -- see [[HA Cluster Setup (JBODs)]].


== Network Share Custom Scripts ==
== Network Share call-outs ==


Common arguments to the network share scripts:
{| class="wikitable"
! Script !! Fires !! Arguments
|-
| {{Code|1=share-postcreate.sh}} || After a new Network Share has been created and its client access entries written || {{Code|1=--share-path=PATH --share-name=NAME --share-id=SHAREUUID --pool=POOLUUID}}
|-
| {{Code|1=share-postmodify.sh}} || After a Network Share has been modified || {{Code|1=--share-path=PATH --share-name=NAME --share-id=SHAREUUID --pool=POOLUUID}}
|}


* --share-path=SHAREPATH
The usual reason to use these is to stamp house defaults onto a share that the dialog does not cover -- POSIX permissions, ACLs, extended attributes, an SELinux label, a directory skeleton. {{Code|1=--share-path}} is the full filesystem path, so the script can act on it directly. See [[Network Shares]] for the share settings themselves.
Full path to where the share is located.


* --share-name=SHARENAME
== Snapshot and replication schedule call-outs ==
Name of the share.


* --share-id=SHAREUUID
These exist so that a database or application can be quiesced before a scheduled snapshot and released afterwards. QuantaStor takes the snapshots of all the Storage Volumes and Network Shares in a schedule as a consistency group rather than one at a time, so the quiesce window your script has to hold open is short.
UUID of the share in the QuantaStor grid database.


* --pool=POOLUUID
{| class="wikitable"
UUID of the storage pool in the QuantaStor grid database.
! Script !! Fires !! Arguments
|-
| {{Code|1=schedule-prestart.sh}} || When a snapshot or replication schedule begins a run, before any per-member work || {{Code|1=--name=SCHEDULE_NAME --id=SCHEDULE_ID}}
|-
| {{Code|1=schedule-presnap.sh}} || After all snapshot tasks are staged and immediately before they are released as a group || {{Code|1=--name=SCHEDULE_NAME --id=SCHEDULE_ID}}
|-
| {{Code|1=schedule-postsnap.sh}} || After every snapshot in the group has been taken, before replication transfers begin || {{Code|1=--name=SCHEDULE_NAME --id=SCHEDULE_ID}}
|}


==== /var/opt/osnexus/custom/share-postcreate.sh ====
{{Code|1=schedule-prestart.sh}} fires for both [[Snapshot Schedules|snapshot schedules]] and [[Remote-replication (DR)|replication schedules]] and is the reliable one of the three. It is called early enough to do preparation work, but not tightly enough around the snapshot itself to serve as a freeze point.


Called just after a new Network Share has been created. This can be used to customize the share permissions and extended attributes with defaults.
'''{{Code|1=schedule-presnap.sh}} and {{Code|1=schedule-postsnap.sh}} do not fire for schedules whose members are on a ZFS pool.''' The snapshot schedule takes a separate code path for ZFS volumes and shares and returns before reaching the point where these two call-outs are invoked. Because virtually all snapshot schedules contain ZFS members, in practice these hooks are not usable today. This is tracked as QSTOR-12378. Do your quiesce work from {{Code|1=schedule-prestart.sh}} until it is resolved, and be aware that the window between {{Code|1=schedule-prestart.sh}} and the snapshots being taken is wider than the presnap window would have been.


==== /var/opt/osnexus/custom/share-postmodify.sh ====
Both call-outs are checked for existence before QuantaStor attempts them, so a schedule run logs nothing about them if you have not installed them.


Called just after a new Network Share has been modified.  This can be used to customize the share permissions and extended attributes with defaults.
== DR failover call-outs ==


== Software Adapter Custom Scripts ==
{| class="wikitable"
! Script !! Fires !! Arguments
|-
| {{Code|1=dr-prefailover.sh}} || At the start of the Activate Checkpoints task on a replication schedule || {{Code|1=--schedule=SCHEDULE_ID}}
|-
| {{Code|1=dr-postfailover.sh}} || After the checkpoints are activated, the aliases are created and the CIFS and NFS configurations have been rewritten || {{Code|1=--schedule=SCHEDULE_ID}}
|}


QuantaStor has a built-in software adapter feature for connecting to other systems via iSCSI and NVMe-oF (TCP and RDMA).  These scripts get called pre and post connection to the storage in case any additional operations need to be orchestrated at the same time.
Activating checkpoints is what turns a DR site's replicas into live, servable shares and volumes, so {{Code|1=dr-postfailover.sh}} is the point at which to repoint an application, update DNS, or start services that depend on the DR copy. See [[Remote-replication (DR)]] for the failover procedure these bracket.


==== /var/opt/osnexus/custom/swadapter-preconnect.sh ====
Both are subject to the five-minute synchronous timeout, which matters more here than elsewhere: a DR failover is exactly when someone is watching the clock. Use the {{Code|1=-async.sh}} form for anything that involves waiting on an external system.


Called before connecting to the target devices.  This script is called with arguments indicating the adapter that is connecting and the IP address of the remote SAN or NVMeoF appliance it is connecting to.
== Software adapter call-outs ==


Arguments:
QuantaStor's software adapter feature connects the appliance to other systems as an initiator over iSCSI and NVMe-oF (TCP and RDMA). These call-outs bracket the login step so that anything that has to be orchestrated alongside the connection can be.
* --adapter=ADAPTERNAME
* --id=ADAPTERUUID
* --ip-address=IPADDRESS


==== /var/opt/osnexus/custom/swadapter-postconnect.sh ====
{| class="wikitable"
! Script !! Fires !! Arguments
|-
| {{Code|1=swadapter-preconnect.sh}} || Before logging in to the adapter's target devices || {{Code|1=--adapter=ADAPTERNAME --id=ADAPTERUUID --ip-address=IPADDRESS}}
|-
| {{Code|1=swadapter-postconnect.sh}} || After the logins have been issued || {{Code|1=--adapter=ADAPTERNAME --id=ADAPTERUUID --ip-address=IPADDRESS}}
|}


Called after connecting to the target devices. This script is called with arguments indicating the adapter that is connecting and the IP address of the remote SAN or NVMeoF appliance it is connecting to.
These fire on a boot-time login, on a login you force, and on a repair pass that finds a target to repair -- not on every adapter scan. A steady-state system with all targets connected calls neither script.


Arguments:
== Other extension points ==
* --adapter=ADAPTERNAME
* --id=ADAPTERUUID
* --ip-address=IPADDRESS


== Snapshot & Replication Schedule Custom Scripts ==
Three more places let you supply your own code. They do not use the call-out mechanism described above -- different directory conventions, different argument styles, no {{Code|1=-async}} variants -- so treat them separately.


These scripts provide a mechanism for administrators to customize the Remote Replication and Snapshot Schedule features available with QuantaStor with custom scripting which is invoked at various stages of the process. Starting with QuantaStor 4.0.3 additional functionality has been added to the scheduling features for remote replication and snapshots to take all of the snapshots for Network Shares and Storage Volumes at the same time in order to provide a consistent point-in-time view.  We refer to the group of snapshots as a consistency group. Consistency groups are generally accurate to within 1 second across all devices in the Snapshot Schedule / Replication Schedule. For releases Prior to 4.0.3, there was no consistency group and snapshots were taken serially which creates problems for OLTP and other workloads.
=== Backup policy post-job handler ===


==== /var/opt/osnexus/custom/schedule-prestart.sh ====
A [[Backup Policies|Backup Policy]] job runs your script after the transfer completes, if it exists:


Command Arguments:
<pre style="font-size: smaller">
/var/opt/osnexus/custom/post-backupjob.sh
</pre>


'''--name=SCHEDULE_NAME --id=SCHEDULE_ID'''
It is invoked by the backup job itself rather than by the service, with short-form arguments and no timeout wrapper:


This script is called just before a snapshot or replication schedule is triggered / executed.  This can provide functionality to begin a freeze of your database or application datastore or provide any other customization that your team may require related to the start of a replication or snapshot schedule.
{| class="wikitable"
! Argument !! Meaning
|-
| {{Code|1=-p POLICYNAME}} || Name of the Backup Policy as it appears in the web interface
|-
| {{Code|1=-b BACKUPDIR}} || Directory the backup was written to
|-
| {{Code|1=-m SOURCEMOUNTDIR}} || Read-only source directory that was backed up
|-
| {{Code|1=-s SUBPATH}} || Sub-path within the remote storage mount
|-
| {{Code|1=-l BACKUPLOG}} || List of files that were backed up; passed only when the policy is configured to create a backup log
|}


==== /var/opt/osnexus/custom/schedule-presnap.sh ====
A working example ships on every appliance and is the best starting point -- copy it, drop the {{Code|1=.example}} suffix, and replace the body:


Command Arguments:
<pre style="font-size: smaller">
/var/opt/osnexus/custom/post-backupjob.sh.example
</pre>


'''--name=SCHEDULE_NAME --id=SCHEDULE_ID'''
Its second worked example is genuinely useful in its own right: it prunes the empty directories a file-selection filter leaves behind in the backup target, guarding the operation with a hold file so a recursive {{Code|1=rmdir -p}} cannot delete the backup directory itself.


This script is called just before snapshots are taken in a snapshot or replication schedule. If you have multiple Storage Volumes and/or Network Shares in the Schedule, QuantaStor will do all prep work related to metadata and QuantaStor Management Objects structures for the Shares/Volumes in the schedule, then trigger your script and finally trigger the snapshots of the Network Shares or Storage Volumes as a group. This ''schedule-presnap.sh'' script can provide functionality to begin a 'freeze' of your database or application datastore or provide any other customization that your team may require for snapshotting of your data.
=== Alternate pool import script ===


==== /var/opt/osnexus/custom/schedule-postsnap.sh ====
If a script exists at the path below, QuantaStor calls it '''instead of''' running its own {{Code|1=zpool import}} when starting a ZFS pool, passing the zpool name and adding {{Code|1=-f}} when the import is being forced:


Command Arguments:
<pre style="font-size: smaller">
/var/opt/osnexus/custom/qs_poolimport.sh
</pre>
 
This is an override, not a call-out: get it wrong and the pool does not import. It exists for unusual device-path and multipath situations, and it bypasses the cache-file and device-path handling QuantaStor normally applies. We recommend contacting support before using it.


'''--name=SCHEDULE_NAME --id=SCHEDULE_ID'''
=== Alert handlers ===


This script is called just after snapshots are taken in a snapshot or replication schedule. If you have multiple Storage Volumes and/or Network Shares in the Schedule, QuantaStor will execute this script just after the snapshots have been taken for the schedule. and then perform final task cleanup tasks and/or begin remote replication tasks to transfer the data to a QuantaStor node at a DR site. This ''schedule-postsnap.sh'' script can provide functionality to begin a 'thaw' of your database or application datastore or provide any other customization that your team may require for operations after snapshotting of your data has occurred.
Alerts have their own extension mechanism -- a handler script in {{Code|1=/var/opt/osnexus/alerthandlers}}, registered through a configuration file, with per-vendor examples shipped as working reference implementations. It is documented under [[Call-home / Alerting#Custom Alert Handlers|Custom Alert Handlers]] on the [[Call-home / Alerting]] page. The security guidance in [[#Securing the call-out directory|Securing the call-out directory]] applies to that directory too, and for the same reason.


==== /var/opt/osnexus/custom/dr-prefailover.sh ====
== Calling the QuantaStor API from a call-out ==


Command Arguments:
Most call-outs are handed a UUID rather than the object itself, so a useful script generally starts by asking QuantaStor for the detail. The {{Code|1=qs}} CLI is installed on the appliance and works from inside a call-out:


'''--schedule=SCHEDULE_ID'''
<pre style="font-size: smaller">
qs pool-get --pool=$POOLID --server=127.0.0.1,admin,PASSWORD --json
</pre>


This script is called when the Activate Checkpoints task is started so that any pre DR-failover activities can be run before the checkpoints are activated.
Note the argument form: it is {{Code|1=[[QuantaStor CLI Command Reference#pool-get|qs pool-get]] --pool=&lt;id&gt;}}, not a positional UUID. Every command's exact argument list is in the [[QuantaStor CLI Command Reference]], and {{Code|1=qs help --min}} lists the full command set on the appliance itself. {{Code|1=--server}} and {{Code|1=--json}} are global to the CLI rather than arguments of a particular command, so they can be added to any of them.


==== /var/opt/osnexus/custom/dr-postfailover.sh ====
Three things to get right when scripting the CLI:


Command Arguments:
* '''Supply credentials explicitly.''' With no server argument the CLI falls back to the default administrator account, which will not work on a system whose administrator password has been changed. Pass a comma-separated address, user and password with no spaces, or export the equivalent as {{Code|1=QS_SERVER}} inside the script. Do not rely on {{Code|1=~/.qs.cnf}} -- {{Code|1=HOME}} is empty in the call-out environment.
* '''Any script holding a password must be mode 700.''' See [[#Securing the call-out directory|Securing the call-out directory]].
* '''Ask for JSON or XML output for anything you parse.''' The default table output is formatted for people and its column widths change.


'''--schedule=SCHEDULE_ID'''
For integration from outside the appliance, QuantaStor exposes the same operations over REST; see the [[REST API Reference Guide]]. Call-outs and the REST API complement each other -- the call-out tells your application that something happened, and the API lets it find out what. [[QuantaStor Shell Utilities]] covers the {{Code|1=qs-}} helper commands that are often more convenient than a raw CLI call.


This script is called when the Activate Checkpoints task is completed so that any post DR-failover activities can be run after the checkpoints are activated.
== Example script ==


== Example bash scripts ==
A starting point that parses the two most common argument styles and does nothing else. Copy it to the call-out name you need, make it root-owned and mode 755, and put your work where the comment is.


Below are some simple sample bash scripts that you can use to get started.   
<pre style="font-size: smaller">
#!/usr/bin/env bash
# QuantaStor custom call-out skeleton.
# Environment note: runs as root, cwd /opt/osnexus/quantastor/bin, no HOME,
# minimal PATH.  Use absolute paths.


==== Custom Scripts requiring POOLID ====
set -u
LOGFILE=/var/log/qs/my-callout.log


<pre>
POOLID=""
#!/usr/bin/env bash
SCHEDULE_NAME=""
SCHEDULE_ID=""


while [ $# -gt 0 ]; do
while [ $# -gt 0 ]; do
   case "$1" in
   case "$1" in
     --pool*|-p*)
     --pool=*)     POOLID="${1#*=}" ;;
      if [[ "$1" != *=* ]]; then shift; fi # Value is next arg if no `=`
    --id=*)       SCHEDULE_ID="${1#*=}" ;;
       POOLID="${1#*=}"
     --schedule=*) SCHEDULE_ID="${1#*=}" ;;
      ;;
    --name=*)    SCHEDULE_NAME="${1#*=}" ;;
     --help|-h)
     *)           echo "ignoring unrecognized argument '$1'" >&2 ;;
      printf "# Help message\n"
      printf "--pool or -p # Pass in pool id.\n"
      printf "*******************************\n"
      exit 0
      exit 0
      ;;
     *)
      >&2 printf "Error: Invalid argument\n"
      exit 1
      ;;
   esac
   esac
   shift
   shift
done
done


# print out POOLID variable for script testing
{
echo "pool-id: $POOLID"
  echo "[$(/bin/date -Is)] called as $0"
  echo " pool=$POOLID name=$SCHEDULE_NAME id=$SCHEDULE_ID"
} >> "$LOGFILE" 2>&1


## execute other custom scripting
## Put your custom work here.  Keep it short if this is the synchronous form:
## the operation that called you is waiting, and you have five minutes.


## exit cleanly when done
exit 0
exit 0
</pre>
Ignoring an unrecognized argument rather than failing on it is deliberate. QuantaStor has added arguments to call-outs across releases, and a script that exits non-zero on an unexpected one produces a warning in the service log on every single invocation.


== Troubleshooting a call-out that is not firing ==
'''Start with the service log.''' Every attempt is recorded, successful or not, tagged {{Code|1=custom_script_manager}}:
<pre style="font-size: smaller">
grep custom_script_manager /var/log/qs/qs_service.log
</pre>
A successful synchronous run looks like this -- note that the command QuantaStor actually ran is quoted in full, including the {{Code|1=timeout}} wrapper and every argument, which is the fastest way to confirm what your script was handed:
<pre style="font-size: smaller">
INFO custom_script_manager  Running backgrounded custom script: '/usr/bin/timeout -k 15s 5m /var/opt/osnexus/custom/schedule-prestart.sh --name=nightly --id=67f0dc8a-...'
INFO custom_script_manager  Custom script '/var/opt/osnexus/custom/schedule-prestart.sh' completed.
</pre>
</pre>


The "backgrounded" wording is used for both forms and is wrong for the synchronous one (QSTOR-12446); tell them apart by the {{Code|1=timeout}} prefix, which only the synchronous form has, or by the trailing {{Code|1=&}}, which only the asynchronous form has.
Then work through the usual causes:
{| class="wikitable"
! Symptom in the log !! Cause !! Fix
|-
| {{Code|1=completed with error '126'}} and {{Code|1=Permission denied}} || The file is not executable || {{Code|1=chmod 755 &lt;script&gt;}}
|-
| {{Code|1=completed with error '127'}} and {{Code|1=No such file or directory}}, but the file is plainly there || Windows line endings, so the interpreter named on the shebang line has a trailing carriage return; or a shebang pointing at an interpreter that is not installed || {{Code|1=dos2unix &lt;script&gt;}}, and check the shebang path exists
|-
| No line for the script at all || QuantaStor never found the file. Most often the name is wrong -- a missing {{Code|1=.sh}}, a hyphen where an underscore was typed, or the file left in place with a {{Code|1=.example}} or {{Code|1=.txt}} suffix || Compare the name against this page, character for character
|-
| No line for the script, name confirmed correct || The event you expected did not actually reach that call-out. Several hooks are narrower than they look -- see the notes in each section || Trigger the operation deliberately from the CLI and watch the log live
|-
| The script runs but behaves differently than from your shell || The call-out environment, not your login environment || Test with {{Code|1=env -i /var/opt/osnexus/custom/&lt;script&gt; &lt;args&gt;}} from {{Code|1=/opt/osnexus/quantastor/bin}} as root
|}
A missing script is a normal, expected condition -- QuantaStor logs it at debug level and carries on -- so do not read the absence of an error as evidence that your script ran.
The most reliable way to prove a call-out fires at all is a stub that appends its arguments and a timestamp to a file, installed at the hook name, then triggering the operation from the CLI and reading the file. Once that works, replace the stub with the real thing.


== Call-outs and upgrades ==


==== Custom Scripts requiring SCHEDULE_NAME or SCHEDULE_ID ====
Call-out scripts live under {{Code|1=/var/opt}} and are not owned by any package, so '''a QuantaStor upgrade leaves them in place''', along with any directory permissions you have tightened. Only two files in the directory belong to the product: {{Code|1=post-backupjob.sh.example}} and the {{Code|1=system-poststart-async.sh}} symlink.
<pre>
#!/usr/bin/env bash


while [ $# -gt 0 ]; do
Two things to do after an upgrade all the same:
  case "$1" in
    --name*)
      if [[ "$1" != *=* ]]; then shift; fi
      SCHEDULE_NAME="${1#*=}"
      ;;
    --id*|--schedule*)
      if [[ "$1" != *=* ]]; then shift; fi
      SCHEDULE_ID="${1#*=}"
      ;;
    --help|-h)
      printf "# Help message\n"
      printf "--name # Pass in schedule name.\n"
      printf "--id or --schedule # Pass in schedule id.\n"
      printf "*******************************\n"
      exit 0
      ;;
    *)
      >&2 printf "Error: Invalid argument\n"
      exit 1
      ;;
  esac
  shift
done


# print out SCHEDULE_ID and SCHEDULE_NAME variables for script testing
* '''Re-check {{Code|1=system-poststart-async.sh}}''' if you were using that name. The package replaces it on every install; see [[#system-poststart.sh|system-poststart.sh]] above.
echo "name: $SCHEDULE_NAME"
* '''Re-read this page's argument lists.''' Arguments have been added to call-outs between releases, and a script written against an older set still runs -- it just ignores information it could be using.
echo "schedule: $SCHEDULE_ID"
echo "id: $SCHEDULE_ID"


## execute other custom scripting
== Related pages ==


## exit cleanly when done
* [[Call-home / Alerting]] -- alert delivery, ITSM integrations, and the [[Call-home / Alerting#Custom Alert Handlers|custom alert handler]] mechanism
exit 0
* [[Snapshot Schedules]] -- the schedules that drive {{Code|1=schedule-prestart.sh}}
* [[Remote-replication (DR)]] -- replication schedules and the Activate Checkpoints failover that the {{Code|1=dr-}} call-outs bracket
* [[HA Cluster Setup (JBODs)]] -- HA failover, where the {{Code|1=pool-prefailover.sh}} and {{Code|1=pool-postfailover.sh}} call-outs run
* [[Backup Policies]] -- the policies that invoke {{Code|1=post-backupjob.sh}}
* [[Network Shares]] -- share settings that {{Code|1=share-postcreate.sh}} is typically used to extend
* [[Storage Pools]] -- pool start, stop, export and scrub operations
* [[Security Configuration]] -- appliance security controls, including the System Use Notification passed to {{Code|1=security-message-update.sh}}
* [[QuantaStor Shell Utilities]] -- the {{Code|1=qs-}} helper commands available to a call-out
* [[QuantaStor CLI Command Reference]] -- full argument lists for every {{Code|1=qs}} command
* [[REST API Reference Guide]] -- the same operations over REST, for integration from outside the appliance


</pre>
----
<small>''Verified against QuantaStor 6.9.0.''</small>

Latest revision as of 15:34, 4 September 2026


QuantaStor has script call-outs which you can use to extend the functionality of the system for integration with custom applications. A call-out is a shell script you place at a fixed path on the appliance; QuantaStor runs it as root at a defined point in a storage operation -- before a pool starts, after a share is created, around a snapshot schedule, either side of an HA failover -- and passes it the identity of the object being operated on.

Because these scripts run as root and are triggered by ordinary storage activity, read Securing the call-out directory before you install your first one.

Section Purpose
How call-outs work Naming, synchronous versus asynchronous, the environment scripts run in, what happens to a non-zero exit
Securing the call-out directory Ownership and permissions, and why a writable call-out directory is a root escalation path
Storage System call-outs Service start, chassis beacon, system use notification
Network port call-outs Either side of bringing an interface up
Storage Pool call-outs Start, stop, export, HA failover, scrub
Network Share call-outs Share create and modify
Snapshot and replication schedule call-outs Quiesce and thaw around scheduled snapshots and replication
DR failover call-outs Either side of Activate Checkpoints
Software adapter call-outs Either side of iSCSI and NVMe-oF logins
Other extension points Backup policy post-job handler, alternate pool import script, alert handlers
Calling the QuantaStor API from a call-out Getting object detail back out of QuantaStor from inside your script
Example script A starting-point script that parses the arguments
Troubleshooting a call-out that is not firing The log to read and the three things that are usually wrong
Call-outs and upgrades What survives a package upgrade and the one name that does not

How call-outs work

Where scripts go, and what they are called

Every call-out lives in one directory and its file name is fixed. QuantaStor looks for the exact name; there is no registration step and no configuration file.

/var/opt/osnexus/custom

Each call-out has two possible forms, and the suffix decides how it runs:

Form How it runs
<hook>.sh Synchronously. The operation that triggered it waits for the script to finish. Wrapped in /usr/bin/timeout -k 15s 5m, so the script gets SIGTERM after five minutes and SIGKILL fifteen seconds after that.
<hook>-async.sh In the background. The operation does not wait, there is no timeout, and the script can outlive the operation entirely.

If both forms exist QuantaStor runs both, starting the asynchronous one first and then the synchronous one. Use the asynchronous form for anything slow, and the synchronous form only when the work genuinely has to complete before the operation continues.

A synchronous script blocks for its full runtime. A poolscrub-prestart.sh that sleeps for 25 seconds makes qs pool-scrub-start take 27 seconds instead of about one. Under HA failover and pool start, that delay is added to your recovery time, so keep synchronous scripts short.

The environment scripts run in

Call-outs are executed by the QuantaStor service, so they inherit its environment and not a login shell's:

Value
User root (uid 0), always
Working directory /opt/osnexus/quantastor/bin
PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
HOME, USER empty
TERM dumb
LANG C.UTF-8

There are only nine environment variables in total and none of your shell profile is loaded, so use absolute paths for every command and file your script touches, and do not rely on $HOME or on anything a login would have set. Note in particular that $HOME is an empty string, which breaks any tool configured through a path built from it.

Exit codes are logged, not acted on

A non-zero exit from a synchronous call-out is recorded as a warning together with the script's standard error, and the operation continues regardless. A call-out cannot veto a pool start, a failover or a snapshot. A script that exits 42 during a pool scrub start produces this and the scrub proceeds:

WARN custom_script_manager  <the script's stderr, line by line>
WARN custom_script_manager  Custom script '/var/opt/osnexus/custom/poolscrub-prestart.sh' completed with error '42'.

The practical consequence is that a broken call-out is invisible from the web interface and from the task list. Nothing turns red. The only place it shows up is the service log -- see Troubleshooting a call-out that is not firing.

Grid systems

A call-out runs on the node that is performing the operation, using that node's copy of the script. In a grid, install your scripts on every node that could own the object, or the call-out will fire on some failovers and pool moves and not others.

Turning call-outs off

Call-outs are on whenever the directory exists. To disable the mechanism entirely -- for a support investigation, say -- the QuantaStor service accepts a disable-custom-scripts-manager startup argument. We recommend contacting support before changing service startup arguments.

Securing the call-out directory

The call-out directory is a root execution path. Anything in it named after a hook is run as root the next time an ordinary, unprivileged storage event occurs -- a pool starting, a network port coming up, a snapshot schedule triggering, a share being created. Write access to that directory is therefore equivalent to root on the appliance, and it needs to be treated the way you would treat /etc/sudoers.d.

Check the directory permissions before you install anything. Current releases ship /var/opt/osnexus/custom world-writable (mode 0777) on a fresh install, which is more permissive than intended -- scripts placed there are modifiable by any account on the system, and call-outs run as root. This is a packaging defect, tracked as QSTOR-12443. Repair it, and repair the sibling directories that have the same problem:

chown root:root /var/log/qs /var/opt/osnexus/custom /var/opt/osnexus/alerthandlers /var/opt/osnexus/quantastor
chmod 755 /var/log/qs /var/opt/osnexus/custom /var/opt/osnexus/alerthandlers /var/opt/osnexus/quantastor

Then set each script to mode 755 and root ownership, so that no non-root account can modify a script that root is going to execute:

chown root:root /var/opt/osnexus/custom/*.sh
chmod 755 /var/opt/osnexus/custom/*.sh

If a script contains sensitive information -- a plain text password, an API token, a private key path -- use 700 rather than 755 so only root can read it. The service runs as root, so 700 does not stop the call-out from working. Better still, keep the secret in a separate root-only file and read it from the script.

Two further points worth making:

  • Hardening survives an upgrade. A directory mode you set by hand is preserved across a package upgrade, so this is a one-time repair per appliance rather than something to redo each release.
  • Anyone who can write a call-out owns the appliance. That includes any service account with write access to the directory and any process running as a non-root user that can reach it. If you are auditing privilege escalation paths on a QuantaStor system, this directory belongs on the list along with the alert handler directory.

See Security Configuration for the appliance's wider security controls.

Storage System call-outs

Script Fires Arguments
system-poststart.sh Every time the QuantaStor service finishes starting --firstboot, only when the appliance itself has rebooted since the previous start; otherwise no arguments
system-ident-start.sh Before the Storage System beacon task begins its LED blink and audible beep pattern none
system-ident-stop.sh After the beacon task completes none
security-message-update.sh When a non-empty System Use Notification is applied --message="TEXT"

system-poststart.sh

This script runs on every service start, including a plain service restart with no reboot. What changes between the two cases is the argument: QuantaStor compares the current boot time against the stamp it keeps at /var/opt/osnexus/quantastor/qs_lastreboot and passes --firstboot only when the appliance has actually rebooted. Branch on that argument if you want work that happens once per boot rather than once per service start.

system-poststart-async.sh is reserved by the product and is not available to use. QuantaStor installs its own Ceph OSD activation script at that name as a symlink, and the package replaces whatever is there on every install and upgrade, silently. Use the synchronous system-poststart.sh instead, or start your own background work from it. This collision is tracked as QSTOR-12444.

system-prestop.sh

This call-out does not currently fire. The hook is implemented in the service but nothing invokes it, so a script installed at this name never runs on a shutdown or restart, whether initiated from the web interface, the CLI or the console. Tracked as QSTOR-12445. If you need work done before a controlled shutdown, drive it from your own orchestration rather than from this call-out.

security-message-update.sh

Called with the System Use Notification text configured under Security Configuration, so you can mirror the same banner into other subsystems -- a GNOME classification banner, for example, or a login screen you manage yourself. The text is sanitized before it reaches your script: double quotes become single quotes, backslashes become forward slashes, and non-ASCII characters are removed. The call-out is not run when the notification is cleared; QuantaStor removes /etc/issue.notice instead.

Network port call-outs

Script Fires Arguments
port-prestart.sh Immediately before QuantaStor brings a port up --name=PORTNAME
port-poststart.sh After the port is up and its configuration has been reconciled --name=PORTNAME

port-prestart.sh can fire more than once for a single port activation. When the port is a bond, QuantaStor cycles the slave interfaces first and calls the script around that step as well as around the bond itself. Write the script so that running it twice in a row is harmless.

Storage Pool call-outs

These are the call-outs to use when an application on the appliance has to attach to or detach from a pool, or from a directory inside one, in step with the pool's lifecycle. All of them take the pool's UUID.

Script Fires Arguments
pool-poststart.sh After a pool has been started --pool=POOLUUID
pool-prestop.sh Before a pool is stopped --pool=POOLUUID
pool-preexport.sh Before a pool export begins --pool=POOLUUID
pool-postexport.sh After the export finishes, whether or not it succeeded --pool=POOLUUID
pool-prefailover.sh Before an HA failover of the pool starts --pool=POOLUUID
pool-postfailover.sh After the failover completes, whether or not it succeeded --pool=POOLUUID
poolscrub-prestart.sh Before a pool scrub is started --name=POOLNAME --id=POOLUUID
poolscrub-poststart.sh Just after the scrub has been started --name=POOLNAME --id=POOLUUID

Points worth knowing:

  • The post script always runs. Export and failover are paired so that the post call-out fires even when the operation throws partway through. Write pool-postexport.sh and pool-postfailover.sh as cleanup that must be safe on both the success and failure paths, and do not treat being called as proof the operation worked -- query the pool state if you need to know.
  • poolscrub-poststart.sh fires when the scrub has been started, not finished. A scrub runs for hours; the call-out returns as soon as the scrub is under way. There is no scrub-completion call-out.
  • pool-poststart.sh also covers CephFS pools, firing when the pool is mounted on its owning node. pool-prestop.sh fires for every pool stop regardless of pool type. The scrub call-outs are ZFS-only, because a scrub request against a CephFS pool is rejected before they are reached -- Ceph scrubbing is driven by policy on the underlying OSDs instead.
  • Keep pool-prefailover.sh and pool-poststart.sh fast if they are synchronous. Both sit directly in the HA recovery path -- see HA Cluster Setup (JBODs).

Network Share call-outs

Script Fires Arguments
share-postcreate.sh After a new Network Share has been created and its client access entries written --share-path=PATH --share-name=NAME --share-id=SHAREUUID --pool=POOLUUID
share-postmodify.sh After a Network Share has been modified --share-path=PATH --share-name=NAME --share-id=SHAREUUID --pool=POOLUUID

The usual reason to use these is to stamp house defaults onto a share that the dialog does not cover -- POSIX permissions, ACLs, extended attributes, an SELinux label, a directory skeleton. --share-path is the full filesystem path, so the script can act on it directly. See Network Shares for the share settings themselves.

Snapshot and replication schedule call-outs

These exist so that a database or application can be quiesced before a scheduled snapshot and released afterwards. QuantaStor takes the snapshots of all the Storage Volumes and Network Shares in a schedule as a consistency group rather than one at a time, so the quiesce window your script has to hold open is short.

Script Fires Arguments
schedule-prestart.sh When a snapshot or replication schedule begins a run, before any per-member work --name=SCHEDULE_NAME --id=SCHEDULE_ID
schedule-presnap.sh After all snapshot tasks are staged and immediately before they are released as a group --name=SCHEDULE_NAME --id=SCHEDULE_ID
schedule-postsnap.sh After every snapshot in the group has been taken, before replication transfers begin --name=SCHEDULE_NAME --id=SCHEDULE_ID

schedule-prestart.sh fires for both snapshot schedules and replication schedules and is the reliable one of the three. It is called early enough to do preparation work, but not tightly enough around the snapshot itself to serve as a freeze point.

schedule-presnap.sh and schedule-postsnap.sh do not fire for schedules whose members are on a ZFS pool. The snapshot schedule takes a separate code path for ZFS volumes and shares and returns before reaching the point where these two call-outs are invoked. Because virtually all snapshot schedules contain ZFS members, in practice these hooks are not usable today. This is tracked as QSTOR-12378. Do your quiesce work from schedule-prestart.sh until it is resolved, and be aware that the window between schedule-prestart.sh and the snapshots being taken is wider than the presnap window would have been.

Both call-outs are checked for existence before QuantaStor attempts them, so a schedule run logs nothing about them if you have not installed them.

DR failover call-outs

Script Fires Arguments
dr-prefailover.sh At the start of the Activate Checkpoints task on a replication schedule --schedule=SCHEDULE_ID
dr-postfailover.sh After the checkpoints are activated, the aliases are created and the CIFS and NFS configurations have been rewritten --schedule=SCHEDULE_ID

Activating checkpoints is what turns a DR site's replicas into live, servable shares and volumes, so dr-postfailover.sh is the point at which to repoint an application, update DNS, or start services that depend on the DR copy. See Remote-replication (DR) for the failover procedure these bracket.

Both are subject to the five-minute synchronous timeout, which matters more here than elsewhere: a DR failover is exactly when someone is watching the clock. Use the -async.sh form for anything that involves waiting on an external system.

Software adapter call-outs

QuantaStor's software adapter feature connects the appliance to other systems as an initiator over iSCSI and NVMe-oF (TCP and RDMA). These call-outs bracket the login step so that anything that has to be orchestrated alongside the connection can be.

Script Fires Arguments
swadapter-preconnect.sh Before logging in to the adapter's target devices --adapter=ADAPTERNAME --id=ADAPTERUUID --ip-address=IPADDRESS
swadapter-postconnect.sh After the logins have been issued --adapter=ADAPTERNAME --id=ADAPTERUUID --ip-address=IPADDRESS

These fire on a boot-time login, on a login you force, and on a repair pass that finds a target to repair -- not on every adapter scan. A steady-state system with all targets connected calls neither script.

Other extension points

Three more places let you supply your own code. They do not use the call-out mechanism described above -- different directory conventions, different argument styles, no -async variants -- so treat them separately.

Backup policy post-job handler

A Backup Policy job runs your script after the transfer completes, if it exists:

/var/opt/osnexus/custom/post-backupjob.sh

It is invoked by the backup job itself rather than by the service, with short-form arguments and no timeout wrapper:

Argument Meaning
-p POLICYNAME Name of the Backup Policy as it appears in the web interface
-b BACKUPDIR Directory the backup was written to
-m SOURCEMOUNTDIR Read-only source directory that was backed up
-s SUBPATH Sub-path within the remote storage mount
-l BACKUPLOG List of files that were backed up; passed only when the policy is configured to create a backup log

A working example ships on every appliance and is the best starting point -- copy it, drop the .example suffix, and replace the body:

/var/opt/osnexus/custom/post-backupjob.sh.example

Its second worked example is genuinely useful in its own right: it prunes the empty directories a file-selection filter leaves behind in the backup target, guarding the operation with a hold file so a recursive rmdir -p cannot delete the backup directory itself.

Alternate pool import script

If a script exists at the path below, QuantaStor calls it instead of running its own zpool import when starting a ZFS pool, passing the zpool name and adding -f when the import is being forced:

/var/opt/osnexus/custom/qs_poolimport.sh

This is an override, not a call-out: get it wrong and the pool does not import. It exists for unusual device-path and multipath situations, and it bypasses the cache-file and device-path handling QuantaStor normally applies. We recommend contacting support before using it.

Alert handlers

Alerts have their own extension mechanism -- a handler script in /var/opt/osnexus/alerthandlers, registered through a configuration file, with per-vendor examples shipped as working reference implementations. It is documented under Custom Alert Handlers on the Call-home / Alerting page. The security guidance in Securing the call-out directory applies to that directory too, and for the same reason.

Calling the QuantaStor API from a call-out

Most call-outs are handed a UUID rather than the object itself, so a useful script generally starts by asking QuantaStor for the detail. The qs CLI is installed on the appliance and works from inside a call-out:

qs pool-get --pool=$POOLID --server=127.0.0.1,admin,PASSWORD --json

Note the argument form: it is qs pool-get --pool=<id>, not a positional UUID. Every command's exact argument list is in the QuantaStor CLI Command Reference, and qs help --min lists the full command set on the appliance itself. --server and --json are global to the CLI rather than arguments of a particular command, so they can be added to any of them.

Three things to get right when scripting the CLI:

  • Supply credentials explicitly. With no server argument the CLI falls back to the default administrator account, which will not work on a system whose administrator password has been changed. Pass a comma-separated address, user and password with no spaces, or export the equivalent as QS_SERVER inside the script. Do not rely on ~/.qs.cnf -- HOME is empty in the call-out environment.
  • Any script holding a password must be mode 700. See Securing the call-out directory.
  • Ask for JSON or XML output for anything you parse. The default table output is formatted for people and its column widths change.

For integration from outside the appliance, QuantaStor exposes the same operations over REST; see the REST API Reference Guide. Call-outs and the REST API complement each other -- the call-out tells your application that something happened, and the API lets it find out what. QuantaStor Shell Utilities covers the qs- helper commands that are often more convenient than a raw CLI call.

Example script

A starting point that parses the two most common argument styles and does nothing else. Copy it to the call-out name you need, make it root-owned and mode 755, and put your work where the comment is.

#!/usr/bin/env bash
# QuantaStor custom call-out skeleton.
# Environment note: runs as root, cwd /opt/osnexus/quantastor/bin, no HOME,
# minimal PATH.  Use absolute paths.

set -u
LOGFILE=/var/log/qs/my-callout.log

POOLID=""
SCHEDULE_NAME=""
SCHEDULE_ID=""

while [ $# -gt 0 ]; do
  case "$1" in
    --pool=*)     POOLID="${1#*=}" ;;
    --id=*)       SCHEDULE_ID="${1#*=}" ;;
    --schedule=*) SCHEDULE_ID="${1#*=}" ;;
    --name=*)     SCHEDULE_NAME="${1#*=}" ;;
    *)            echo "ignoring unrecognized argument '$1'" >&2 ;;
  esac
  shift
done

{
  echo "[$(/bin/date -Is)] called as $0"
  echo "  pool=$POOLID name=$SCHEDULE_NAME id=$SCHEDULE_ID"
} >> "$LOGFILE" 2>&1

## Put your custom work here.  Keep it short if this is the synchronous form:
## the operation that called you is waiting, and you have five minutes.

exit 0

Ignoring an unrecognized argument rather than failing on it is deliberate. QuantaStor has added arguments to call-outs across releases, and a script that exits non-zero on an unexpected one produces a warning in the service log on every single invocation.

Troubleshooting a call-out that is not firing

Start with the service log. Every attempt is recorded, successful or not, tagged custom_script_manager:

grep custom_script_manager /var/log/qs/qs_service.log

A successful synchronous run looks like this -- note that the command QuantaStor actually ran is quoted in full, including the timeout wrapper and every argument, which is the fastest way to confirm what your script was handed:

INFO custom_script_manager  Running backgrounded custom script: '/usr/bin/timeout -k 15s 5m /var/opt/osnexus/custom/schedule-prestart.sh --name=nightly --id=67f0dc8a-...'
INFO custom_script_manager  Custom script '/var/opt/osnexus/custom/schedule-prestart.sh' completed.

The "backgrounded" wording is used for both forms and is wrong for the synchronous one (QSTOR-12446); tell them apart by the timeout prefix, which only the synchronous form has, or by the trailing &, which only the asynchronous form has.

Then work through the usual causes:

Symptom in the log Cause Fix
completed with error '126' and Permission denied The file is not executable chmod 755 <script>
completed with error '127' and No such file or directory, but the file is plainly there Windows line endings, so the interpreter named on the shebang line has a trailing carriage return; or a shebang pointing at an interpreter that is not installed dos2unix <script>, and check the shebang path exists
No line for the script at all QuantaStor never found the file. Most often the name is wrong -- a missing .sh, a hyphen where an underscore was typed, or the file left in place with a .example or .txt suffix Compare the name against this page, character for character
No line for the script, name confirmed correct The event you expected did not actually reach that call-out. Several hooks are narrower than they look -- see the notes in each section Trigger the operation deliberately from the CLI and watch the log live
The script runs but behaves differently than from your shell The call-out environment, not your login environment Test with env -i /var/opt/osnexus/custom/<script> <args> from /opt/osnexus/quantastor/bin as root

A missing script is a normal, expected condition -- QuantaStor logs it at debug level and carries on -- so do not read the absence of an error as evidence that your script ran.

The most reliable way to prove a call-out fires at all is a stub that appends its arguments and a timestamp to a file, installed at the hook name, then triggering the operation from the CLI and reading the file. Once that works, replace the stub with the real thing.

Call-outs and upgrades

Call-out scripts live under /var/opt and are not owned by any package, so a QuantaStor upgrade leaves them in place, along with any directory permissions you have tightened. Only two files in the directory belong to the product: post-backupjob.sh.example and the system-poststart-async.sh symlink.

Two things to do after an upgrade all the same:

  • Re-check system-poststart-async.sh if you were using that name. The package replaces it on every install; see system-poststart.sh above.
  • Re-read this page's argument lists. Arguments have been added to call-outs between releases, and a script written against an older set still runs -- it just ignores information it could be using.

Related pages


Verified against QuantaStor 6.9.0.