Script Steps

After the mapper has delivered its requitition, multiple script steps can be used to customize the result. These script steps can change the requisition initially provided by the mapper. Script steps provide a requisition as their results, which allows the chaining of script steps. Every script step has access to the latest version of the requisition. The first script step reads the requisition from the mapper. The second script step reads the requisition provided by the first script step and so on. Additionally every script step can access the configuration of PRIS and the raw result of the source. The script steps are executed by the JVM following the JSR-223 specification. Therefore all JSR-223 supported languages can be used to write script steps. By default runtimes for Groovy 2.3.3 and Beanshell 2.0b5 are provided out of the box.

Every script step has to provide a Requisition object as its result. For every request of a requisition each script step is reloaded.

Script steps are configured in the requisition.properties for the requisition.

/opt/opennms-pris
└── requisitions
|   └── myRequisitionConfiguration
|       └── requisition.properties
└── scriptsteps
    └── default
    |   └── reverseDNS.groovy
    |   └── requisitionRename.groovy
    |   └── IgnoreNodeByCategory.groovy
    └── custom
        └── myScript.groovy
How to add script steps
### File: requisition.properties

## source configuration part
source = ...

## Run a no operation mapper
mapper = echo

# run script step
script.file = ../../scriptsteps/default/requisitionRename.groovy, ../../scriptsteps/default/IgnoreNodeByCategory.groovy
Example script step 1
/**
* This script renames the requisition to the value in the newName variable.
* If newBuilding is set to true, each node gets the newName set as building
*/

import org.opennms.pris.model.Requisition
import org.opennms.pris.model.RequisitionNode

logger.info("starting requisitionRename.groovy")

String newName = "myNewRequisitionName"
Boolean newBuilding = false // true

requisition.setForeignSource(newName)

if (newBuilding) {
  for (RequisitionNode node : requisition.getNodes()) {
    node.setBuilding(newName)  
  }
}
logger.info("done with requisitionRename.groovy")
return requisition
Example script step 2
/**
* This script step removes every node from the requisiton that has the "ignore" category assigned to it.
* The category match ignores case.
 **/

import org.opennms.pris.model.Requisition
import org.opennms.pris.model.RequisitionNode
import org.opennms.pris.model.RequisitionCategory
import org.opennms.pris.util.RequisitionUtils

logger.info("starting IgnoreNodeByCategory.groovy")
final String IGNORE_CATEGORY = "ignore"
List <RequisitionNode> nodes = new ArrayList<>();

for (RequisitionNode node : requisition.getNodes()) {
    if (RequisitionUtils.hasCategory(node, IGNORE_CATEGORY, true)) {
        logger.debug("node '{}' has to be ignored", node.getForeignId())
    } else {
        nodes.add(node)
        logger.debug("node '{}' is ok", node.getForeignId())
    }
}
requisition.unsetNodes()
requisition.withNodes(nodes)
logger.info("done with IgnoreNodeByCategory.groovy")
return requisition
Example script step 3
/**
* This script step sets the nodelabel based on a reverse dns lookup of the ip interfaces.
* It reverse dns lookups all interfaces for each node until it findes a dns name for a node.
* If a dns name was found it is set as nodelabel and no other interface of the nodes will be checked.
* If no dns name was found the nodelabel will be changed.
*/

import org.opennms.pris.model.Requisition
import org.opennms.pris.model.RequisitionNode
import org.opennms.pris.model.RequisitionInterface

logger.info("starting reverseDNS.groovy")

for (RequisitionNode node : requisition.getNodes()) {
  for (RequisitionInterface myInterface : node.getInterfaces()) {
    String ipAddress = myInterface.getIpAddr()
    String dnsNodeLabel = InetAddress.getByName(ipAddress).getCanonicalHostName()
    logger.debug("For foreignID '{}' dnsNodeLabel for IP '{}' is '{}'", node.getForeignId(), ipAddress, dnsNodeLabel)
    if (!ipAddress.equals(dnsNodeLabel)) {
      logger.info("Using '{}' as NodeLabel for foreignId '{}' based on IP '{}'", dnsNodeLabel, node.getForeignId(), ipAddress)
      node.setNodeLabel(dnsNodeLabel)
      break
    }
  }
}
logger.info("done with reverseDNS.groovy")
return requisition
Example script step 4
/**
* This script forces a hard failure if the requisition is empty.
* This can avoid provisioning an empty requisition,
* including removing the existing nodes, on a failure of a source or mapper.
**/

import org.slf4j.Logger
import org.opennms.pris.model.Requisition

logger.info("starting failOnEmpty.groovy")

logger.debug("Amount of nodes in the requisition '{}'", (requisition.getNodes().size()))

if (requisition.getNodes().size() == 0) {
  throw new Exception("The requisition '" + requisition.getForeignSource()  + "' had no nodes. The failOnEmpty.groovy script is failing the request on purpose.")
}
logger.info("done with failOnEmpty.groovy")

return requisition 
Example script step 5
/**
* This script provides backwards compatibility with OpenNMS 1.12 in regards to Assets.
**/

import org.opennms.pris.model.Requisition
import org.opennms.pris.model.RequisitionNode
import org.opennms.pris.model.RequisitionAsset
import org.opennms.pris.util.RequisitionUtils
import org.opennms.pris.model.AssetField_1_12
import org.opennms.pris.util.AssetUtils

logger.info("starting OpenNMS_Assets_1_12.groovy")

List<RequisitionAsset> assetsToRemove = new ArrayList<>();

for (RequisitionNode node : requisition.getNodes()) {
   for (RequisitionAsset asset : node.getAssets()) {
       if (asset.getName().equalsIgnoreCase("managedObjectInstance") || asset.getName().equalsIgnoreCase("managedObjectType") ) {
           assetsToRemove.add(asset);
           logger.info("Remove from node '{}' the asset '{}' with the value '{}'", node.getNodeLabel(), asset.getName(), asset.getValue());
       } else {
           for (AssetField_1_12 assetField : AssetField_1_12.values()) {
               if (asset.getName().equalsIgnoreCase(assetField.name())) {
                   String assetValue_1_12 = AssetUtils.assetStringCleaner(asset.getValue(), assetField.maxLength);
                   if (!assetValue_1_12.equals(asset.getValue())) {
                       logger.info("For node '{}' asset '{}' was changed from '{}' to '{}'" , node.getNodeLabel(), asset.getName(), asset.getValue(), assetValue_1_12)
                       asset.setValue(assetValue_1_12);
                   }
                   break;
               }
           }
      }
  }
  node.getAssets().removeAll(assetsToRemove);
  assetsToRemove = new ArrayList<>();
}
logger.info("done with OpenNMS_Assets_1_12.groovy")
return requisition

Every script step can reference variables from the runtime of PRIS. The following script shows the provided objects:

Example script step 6
/**
* This sample script step demonstrates all objects provided by the pris runtime.
* The objects are casted to their exact type and are logged.
**/

import java.nio.file.Path
import org.slf4j.Logger
import org.opennms.pris.model.Requisition
import org.opennms.pris.util.InterfaceUtils
import org.opennms.pris.config.InstanceApacheConfiguration

logger.info("starting Sample.groovy")

logger.debug("script '{}'", ((Path)script))

logger.debug("data '{}'", ((Object)data))

logger.debug("requisition '{}'", ((Requisition)requisition))

logger.debug("logger '{}'", ((Logger)logger))

logger.debug("config '{}'", ((InstanceApacheConfiguration)config))

logger.debug("config '{}'", ((InterfaceUtils)interfaceUtils))

logger.info("done with Sample.groovy")

return requisition

The following two script steps provide a mechanism to fail the requisition on purpose if the amount of nodes has changed to drastically to the previous run. The setup contains of two scripts that have to be included as script steps in order.

Example script step 7
/**
* This script persists the amount of nodes delivered for the last request into a previousSize.txt file next to the requisition.properties.
* To configure the script provide a script.sizeChangeAbs parameter in the requisition.properties.
**/

import org.slf4j.Logger
import org.opennms.pris.model.Requisition
import javax.xml.bind.JAXBContext
import java.io.StringWriter

logger.info("starting failOnSizeChange.groovy")
String fileName = "previousSize.txt"
String sizeChangeAbsParam = "sizeChangeAbs"
logger.info("Is '{}' set in config? '{}'", sizeChangeAbsParam ,config.containsKey(sizeChangeAbsParam))

if (config.containsKey(sizeChangeAbsParam)) {
	logger.info("fail on size change bigger than '{}'", config.getInt(sizeChangeAbsParam))
	int sizeChangeThreshold = config.getInt(sizeChangeAbsParam)
	File previousSizeFile = new File("requisitions" + File.separator + requisition.getForeignSource() + File.separator + fileName)
	if (previousSizeFile.exists() && previousSizeFile.canRead()) {
		int previousSize = previousSizeFile.getText('UTF-8').toInteger()
		logger.info("previousSize from file is '{}'. New size is '{}'", previousSize, requisition.getNodes().size())
		int sizeChange = (previousSize - requisition.getNodes().size()).abs()
		if (sizeChange  > sizeChangeThreshold) {
			throw new Exception("The requisition '" + requisition.getForeignSource()  + "' changed size by '" + sizeChange + "' nodes. The configured limit for size change is '" + sizeChangeThreshold + "' Failed on purpose.")
		}
	} else {
		logger.info("No " + fileName + " found. Deliver requisition.")	
		}
}

logger.info("done with failOnSizeChange.groovy")

return requisition 

The persistence of the previous requistion size is provided by the second script step of the setup.

Example script step 8
/**
* This script persists the amount of nodes delivered for the last request into a previousSize.txt file next to the requisition.properties.
**/

import org.slf4j.Logger
import org.opennms.pris.model.Requisition
import javax.xml.bind.JAXBContext
import java.io.StringWriter

logger.info("starting persistRequisitionSize.groovy")

JAXBContext jc = JAXBContext.newInstance(Requisition)
StringWriter sw = new StringWriter()
jc.createMarshaller().marshal(requisition, sw)

String xml = sw.toString()

File file = new File("requisitions" + File.separator  + requisition.getForeignSource() + File.separator + "previousSize.txt")
file.write requisition.getNodes().size() + ""
logger.info("persisted a requisition size of {}", requisition.getNodes().size())
logger.info("done with persistRequisitionSize.groovy")

return requisition 
Example script step 9
/**
* This script step adds every node from the requisiton that has a specific category assigned to it.
 **/

import org.opennms.pris.model.Requisition
import org.opennms.pris.model.RequisitionNode
import org.opennms.pris.model.RequisitionCategory
import org.opennms.pris.util.RequisitionUtils

logger.info("starting selectNodeByCategory.groovy")
final String SELECT_CATEGORY = "CATEGORY"
List <RequisitionNode> nodes = new ArrayList<>();

for (RequisitionNode node : requisition.getNodes()) {
    if (RequisitionUtils.hasCategory(node, SELECT_CATEGORY, true)) {
        logger.debug("node '{}' is ok", node.getForeignId())
        nodes.add(node)
    } else {
        logger.debug("node '{}' has to be ignored", node.getForeignId())
    }
}
requisition.unsetNodes()
requisition.withNodes(nodes)
logger.info("done with selectNodeByCategory.groovy")
return requisition