Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • The below sample implementation is available for download: assertions.zip
  • Unzip the sample to your JobScheduler Master's live folder. This will create an assertions sub-folder with the below job-related objects.
  • The implementation includes 
    • the Assertion Monitor

...

    • script that is used by jobs that signal execution of a job chain,
    • the Assertion Job Chain that handles pairs of Assertion Orders and calls to the Assertion Monitor.

Assertion Monitor

The assertion_shadow_order Assertion Monitor can be used by any job chain of a user that signals execution to the Assertion Job Chain:

Use of the Assertion Monitor by Jobs

The Assertion Monitor is used by any jobs that should signal execution of a job chain that resolves an asserted event.

Code Block
languagexml
titleUse Implementation of the Assertion Monitor with job1
linenumberstrue
collapsetrue
<job<monitor  stop_on_errorordering="no" order="yes0">
    <script  language="shelljava:javascript">
        <![CDATA[
echo "some job"
exit 0
        ]]>
    </script>
    <monitor.use  monitor="resolve_assertion"/>
    <run_time />
</job>

Explanations:

  • The <monitor.use> element references the Assertion Monitor. This monitor ships with theresolve_assertion.monitor.xml file in the assertions directory of the delivery.
  • Should the Assertion Monitor be referenced from jobs outside of the assertions directory then an absolute path to its location can be used like this:
    • <monitor.use monitor="/assertions/resolve_assertion"/>
  • The Assertion Monitor is executed when an order completes a job node. It will create a shadow order for the assertions job chain that signals that the assertion has been met, see below.

Implementation of the Assertion Monitor

The Assertion Monitor is implemented like this:

Code Block
languagexml
titleImplementation of the Assertion Monitor
linenumberstrue
collapsetrue
<monitor  ordering="0">
    <script  language="java:javascript">
        <![CDATA[
function spooler_process_after( spooler_process_result )
{
	// modify the path to the assertion job chain if required
    var job_chain = spooler.job_chain( '/assertions/assertions' );

	// create a new order object
	var order = spooler.create_order();

	// copy the current order's id to the newly created order: !folder[!sub-folder]#job-chain-name#order-id
    var pos = spooler_task.order.job_chain.path.lastIndexOf( '/' );
	order.id = spooler_task.order.job_chain.path.substring( 0, pos ).replace( '/', '!' ) + '#' + spooler_task.order.job_chain.name + '#' + spooler_task.order.id;

    // signal success or failure
	order.params = spooler.create_variable_set();
	order.params.set_var( 'spooler_process_result', spooler_process_result );
		
	if ( spooler_process_result )
	{
		order.title = 'successful: ' + spooler_task.order.title;
	} else {
		order.title = 'failed: ' + spooler_task.order.title;
	}

   	// submit the newly created order
    spooler_log.info( '.. scheduling resolving order for assertion: ' + order.id );
   	job_chain.add_or_replace_order( order );

	return spooler_process_result;
}
        ]]>
    </script>
</monitor>

Explanations:

  • The Monitor creates a shadow order for the assertions job chain.
    • The shadow order id is created from the following components: 
      • the folder of the current job chain (and optional sub-folders): all forward slashes are replaced by exclamation marks (!).
      • separated by a hash character follows the name of the job chain.
      • separated by a hash character follows the original order id.
    • Example:  !my_folder!my_subfolder#my_job_chain#my_order_id
      • the job chain my_job_chain is located in the /my_folder/my_subfolder directory.
      • the current order id is my_order_id.
  • Line 7: should the name or location of the assertions job chain be changed then this should be considered by the Monitor.

Assertion Job Chain

function spooler_process_after( spooler_process_result )
{
	// modify the path to the assertion job chain if required
    var job_chain = spooler.job_chain( '/assertions/assertions' );

	// create a new order object
	var order = spooler.create_order();

	// copy the current order's id to the newly created order: !folder[!sub-folder]#job-chain-name#order-id
    var pos = spooler_task.order.job_chain.path.lastIndexOf( '/' );
	order.id = spooler_task.order.job_chain.path.substring( 0, pos ).replace( '/', '!' ) + '#' + spooler_task.order.job_chain.name + '#' + spooler_task.order.id;

    // signal success or failure
	order.params = spooler.create_variable_set();
	order.params.set_var( 'spooler_process_result', spooler_process_result );
		
	if ( spooler_process_result )
	{
		order.title = 'successful: ' + spooler_task.order.title;
	} else {
		order.title = 'failed: ' + spooler_task.order.title;
	}

   	// submit the newly created order
    spooler_log.info( '.. scheduling resolving order for assertion: ' + order.id );
   	job_chain.add_or_replace_order( order );

	return spooler_process_result;
}
        ]]>
    </script>
</monitor>

Explanations:

  • The Assertion Monitor creates a Shadow Order for the Assertion Job Chain, see below.
    • The Shadow Order id is made up from the following parts: 
      • the folder of the originating job chain (and optional sub-folders): all forward slashes are replaced by exclamation marks (!).
      • separated by a hash character follows the name of the originating job chain.
      • separated by a hash character follows the originating order id.
    • Example:  !my_folder!my_subfolder#my_job_chain#my_order_id
      • the job chain my_job_chain is located in the /my_folder/my_subfolder directory hierarchy.
      • the originating order id is my_order_id.
  • Line 7: should the name or location of the Assertions Job Chain be changed then this should be considered by the Assertions Monitor.

Assertion Job Chain

The assertions job chain implements management of assertions and is provided with the assertions directory of the delivery.

Code Block
languagexml
titleAssertion Job Chain
linenumberstrue
collapsetrue
<job_chain>
    <job_chain_node  state="100" job="assertions" next_state="success" error_state="error" on_error="suspend"/>
    <job_chain_node  state="success"/>
    <job_chain_node  state="error"/>
</job_chain>

Explanations:

  • The job chain includes a single job assertions, see below.
  • The job chain can easily be extended, e.g. by a successor job that is executed in case of failure of the assertions job. The successor job could e.g. implement a mail job to send e-mail notifications.
  • By default the assertions job relies on the fact that either JobScheduler is configured to send e-mail in case of any failed jobs or that the JobScheduler Monitoring Interface is used to forward notifications to a System Monitor, such as Nagios, 

Assertion Job

The assertions job effectively does the work of determining if an expectation has been met, i.e. if for a given Assertion Order one or more Shadow Orders have been generatedThe job chain implements management of assertions and is provided with the assertions directory of the delivery.

Code Block
languagexml
titleAssertion Job Chain
linenumberstrue
collapsetrue
<job_chain>
    <jobstop_chain_node  stateon_error="100no" joborder="assertionsyes" next_statetitle="successManage Assertions" error_state="error" on_error="suspend"/>
    <job_chain_node  state="success"/>>
    <script  language="java:javascript">
        <![CDATA[
function spooler_process()
{
    var jobChainFolder, jobChainName, orderId = null;
    <job_chain_nodevar candidateOrders state="error"/>
</job_chain>

Explanations:

  • The job chain includes a single job assertions, see below.
  • The job chain can easily be extended, e.g. by a successor job that is executed in case of failure of the assertions job. The successor job could e.g. implement a mail job to send e-mail notifications.
  • By default the assertions job relies on the fact that either e-mail is configured to be sent in case of any failed jobs or that the JobScheduler Monitoring Interface is used to forward notifications to a System Monitor, as e.g. Nagios, 

Assertion Job

This job effectively does the work of determining if an expectation has been met.

Code Block
languagexml
titleAssertion Job
linenumberstrue
collapsetrue
<job  stop_on_error="no" order="yes" title="Manage Assertions">
    <settings >
        <log_level ><![CDATA[debug1]]></log_level>
    </settings>
    <script  language="java:javascript">
        <![CDATA[
function spooler_process()
{
    var jobChainFolder, jobChainName, orderId = null;
    var candidateOrders = [];

    // optionally the required number of orders (successful or failed) is specified
    var numOfOrders = 0;
    if ( spooler_task.order.params.value( 'num_of_orders' ) = [];

    // optionally the required number of orders (successful or failed) is specified
    var numOfOrders = 0;
    if ( spooler_task.order.params.value( 'num_of_orders' ) )
    {
        numOfOrders = parseInt( spooler_task.order.params.value( 'num_of_orders' ) );
    }

    // optionally the required number of successful orders is specified
    var numOfSuccessfulOrders = 0;
    if ( spooler_task.order.params.value( 'num_of_successful_orders' ) )
    {
        numOfSuccessfulfOrders = parseInt( spooler_task.order.params.value( 'num_of_successful_orders' ) );
    }

    if ( numOfSuccessfulOrders > 0 )
    {
        numOfOrders = numOfSuccessfulOrders;
    }

    var parts = spooler_task.order.id.split( '#' );
    if ( parts.length < 2 )
    {
        numOfOrders = parseInt( spooler_task.order.params.value( 'num_of_orders' )log.error( 'wrong format for order id, use: !folder[!sub-folder]#name[#order_id]: ' + spooler_task.order.id );
    }

    // optionally the required number of successful orders is specified
return false;
    } else {
      var numOfSuccessfulOrders jobChainFolder = parts[0];
        jobChainName = parts[1];

        if ( spooler_task.order.params.value( 'num_of_successful_orders' )parts.length > 2 )
        {
        numOfSuccessfulfOrders  = parseInt( spooler_task.order.params.value( 'num_of_successful_orders' ) ); orderId = parts[2];
        }
    }

    // handle asserting order
    if ( numOfSuccessfulOrders > 0!orderId )
    {
        numOfOrders = numOfSuccessfulOrders;// lookup resolving orders
    }

    var partscommand = spooler_task.order.id.split( '#' )"<show_state subsystems='folder order' what='folders job_chain_orders no_subfolders' path='" + spooler_task.job.folder_path + "'/>";
    if ( parts.length < 2var )
xmlResponse = executeXml( command {);
        spooler_log.error( 'wrong format for order id, use: !folder[!sub-folder]#name[#order_id]: 'var xPath = "//folder[@path='" + spooler_task.job.folder_path + "']/job_chains/job_chain[@path = '" + spooler_task.order.job_chain.idpath );
        return false;
    } else {
        jobChainFolder = parts[0]+ "']/job_chain_node/order_queue/order[@path = '/' and @suspended = 'yes' and starts-with(@id, '" + jobChainFolder + "#" + jobChainName + "#')]";
        jobChainName = parts[1];

        if ( parts.length > 2 )
        {spooler_log.debug( '.. select nodes by xPath: ' + xPath );
        var xmlNodes = xmlResponse.selectNodeList( xPath );
        // traverse node list
 orderId = parts[2];
     for (  }
    }

var xmlIndex=0; xmlIndex < xmlNodes.getLength(); xmlIndex++ )
     // handle asserting order{
    if ( !orderId )
    {
 var xmlNode = xmlNodes.item( xmlIndex );
  // lookup resolving orders
        var commandxmlOrderId = xmlResponse.selectSingleNodeValue( "<show_state subsystems='folder order' what='folders job_chain_orders no_subfolders' path='" + spooler_task.job.folder_path + "'/>";xmlNode, "@id" );

            if ( xmlOrderId == null )
        var xmlResponse = executeXml( command );{
        var xPath = "//folder[@path='" + spooler_task.job.folder_path + "']/job_chains/job_chain[@path = '" + spooler_task.order.job_chain.path + "']/job_chain_node/order_queue/order[@path = '/' and @suspended = 'yes' and starts-with(@id, '" + jobChainFolder + "#" + jobChainName + "#')]";
        spooler_log.debug( '.. select nodes by xPath: ' + xPath );
        var xmlNodes = xmlResponse.selectNodeList( xPath );
      continue;
            }

            if ( numOfSuccessfulfOrders > 0 )
            {
                var xmlOrderTitle // traverse node list
= xmlResponse.selectSingleNodeValue( xmlNode, "@title" );
            for ( var xmlIndex=0; xmlIndexif <( xmlNodesxmlOrderTitle.getLength(); xmlIndex++startsWith( 'failed:' ) )
        {
        {
    var xmlNode = xmlNodes.item( xmlIndex );
            var xmlOrderId = xmlResponse.selectSingleNodeValue( xmlNode, "@id"spooler_log.info( '.. failed resolving order found: ' + xmlOrderId );

            if ( xmlOrderId == null )
            {continue;
                  continue;}
            }

            if ( numOfSuccessfulfOrders > 0 )
candidateOrders[candidateOrders.length] = xmlOrderId;
            spooler_log.info( '.. matching {
resolving order found: ' + xmlOrderId );
        }

   var xmlOrderTitle = xmlResponse.selectSingleNodeValue( xmlNode, "@title" );
         // default: remove all resolving orders for an asserting order
        if ( xmlOrderTitle.startsWith( 'failed:' )numOfOrders == 0 )
        {
        {
    spooler_log.info( '.. all resolving orders will be removed' );
         spooler_log.info( '.. failed resolvingfor( order found: ' + xmlOrderId );var i=0; i<candidateOrders.length; i++ )
            {
          continue;
      spooler_log.info( '.. resolving suspended order is removed: ' +  }candidateOrders[i] );
            }

    command = "<remove_order job_chain='" + spooler_task.order.job_chain.path + "' order='" + candidateOrders[candidateOrders.lengthi] = xmlOrderId+ "'/>";
            spooler_log.info( '.. matching resolving order found: ' +executeXml( xmlOrderIdcommand );
            }

        // default: remove all resolvingif orders( forcandidateOrders.length an< asserting1 order)
        if ( numOfOrders == 0 ){
        {
            spooler_log.infoerror( '.. allno resolving orders found will be removed'for asserting order: ' + spooler_task.order.id );
            for( var i=0; i<candidateOrders.length; i++ )  spooler_task.order.suspended = false;
            {
    spooler_task.order.state = 'error';
          spooler_log.info( '.. resolving suspended order is removed: ' + candidateOrders[i] );
return false;
            }
       command = "<remove_order job_chain='" + spooler_task.order.job_chain.path + "' order='" + candidateOrders[i] + "'/>";
} else {
            // remove the specified number of resolving orders considering use of executeXml( command );the num_of_orders parameter
            }

var k = numOfOrders < candidateOrders.length ? numOfOrders : candidateOrders.length;
           if for( candidateOrders.length < 1var i=0; i<k; i++ )
            {
                spooler_log.errorinfo( 'no.. resolving ordersorder found for asserting orderis removed: ' + spooler_task.order.idcandidateOrders[i] );
                spooler_task.order.suspendedcommand = false;
                "<remove_order job_chain='" + spooler_task.order.state = 'error'.job_chain.path + "' order='" + candidateOrders[i] + "'/>";
                return falseexecuteXml( command );
            }

        } else {
  if ( numOfOrders > candidateOrders.length )
     // remove the specified number of resolving orders{
 considering use of the num_of_orders parameter
          spooler_log.error( 'number varof krequired =orders numOfOrders[' < candidateOrders.length ? + numOfOrders : candidateOrders.length;
            for( var i=0; i<k; i++ )
+ '] exceeds number of resolving oders [' + candidateOrders.length + ']' );
                spooler_task.order.suspended = {false;
                spooler_log.info( '.. resolving order is removed: ' + candidateOrders[i] );
task.order.state = 'error';
                return false;
        command = "<remove_order job_chain='" + spooler_task.order.job_chain.path + "' order='" + candidateOrders[i] + "'/>";  } else if ( numOfOrders == candidateOrders.length ) {
                executeXmlspooler_log.info( command );
            }

            if ( numOfOrders > candidateOrders.length )'number of required orders [' + numOfOrders + '] matches number of resolving orders [' + candidateOrders.length + ']' );
            } else {
                spooler_log.errorinfo( 'number of required orders [' + numOfOrders + '] exceedsis smaller than number of resolving odersorders [' + candidateOrders.length + ']' );
            }
    spooler_task.order.suspended = false;
    }
    } else {
        // handle  spooler_task.order.state = 'error';resolving order
        spooler_log.info( '.. resolving order is suspended: '  return false+ spooler_task.order.id );
        spooler_task.order.suspended = true;
  } else if ( numOfOrders == candidateOrders.length )return {false;
    }

         return true;
}

function executeXml( command ) {
   spooler_log.info( 'number of required orders [' + numOfOrders + '] matches number of resolving orders [' + candidateOrders.length + ']'  var rc = false;

    spooler_log.debug( '.... executing xml command: ' + command );
    var response = spooler.execute_xml(     } else {
           command );

     spooler_log.infodebug( 'number of required orders [.... receiving xml response: ' + numOfOrdersresponse +);
 '] is smaller thanvar numberxmlDOM of= resolving orders [' + candidateOrders.length + ']' );new Packages.sos.xml.SOSXMLXPath( new java.lang.StringBuffer( response ) );

    var errorCode = xmlDOM.selectSingleNodeValue( "//ERROR/@code" );
    var }
errorText = xmlDOM.selectSingleNodeValue( "//ERROR/@text" );
    }
if ( errorCode || }errorText else) {
    {
    // handle resolving order
        spooler_log.infoerror( 'xml response: errorCode='.. resolving+ ordererrorCode is+ suspended:', errorText=' + spooler_task.order.iderrorText );
    }    spooler_task.order.suspended = true;else {
      rc = return falsexmlDOM;
    }

    return truerc;
}

function executeXml( command ) {
    var rc = false;
]]>
    spooler_log.debug( '.... executing xml command: ' + command );</script>
    var response = spooler.execute_xml( command );

    spooler_log.debug( '.... receiving xml response: ' + response );
    var xmlDOM = new Packages.sos.xml.SOSXMLXPath( new java.lang.StringBuffer( response ) );

    var errorCode = xmlDOM.selectSingleNodeValue( "//ERROR/@code" );
    var errorText = xmlDOM.selectSingleNodeValue( "//ERROR/@text" );
    if ( errorCode || errorText ) 
    {
      spooler_log.error( 'xml response: errorCode=' + errorCode + ', errorText=' + errorText );
    } else {
      rc = xmlDOM;
    }

    return rc;
}<run_time />
</job>

Explanations:

  • The job checks if for a given Assertion Order a matching Shadow Order has been created by the Assertion Monitor.

Usage

Use of the Assertion Monitor by Jobs

The Assertion Monitor is used by any jobs that should signal execution of a job chain to the Assertion Job Chain.

Code Block
languagexml
titleUse of the Assertion Monitor with job1
linenumberstrue
collapsetrue
<job  stop_on_error="no" order="yes">
    <script  language="shell">
        <![CDATA[
echo "some job"
exit 0
        ]]>
    </script>
    <monitor.use  monitor="assertion_shadow_order"/>
    <run_time />
</job>

Explanations:

  • The <monitor.use> element references the Assertion Monitor. This monitor ships with the assertion_shadow_order.monitor.xml file in the assertions directory of the delivery.
  • Should the Assertion Monitor be referenced from jobs outside of the assertions directory then an absolute path to its location can be used like this:
    • <monitor.use monitor="/assertions/assertion_shadow_order"/>
  • The Assertion Monitor is executed when an order completes a job node. It will create a Shadow Order for the Assertions Job Chain that signals that an expectation has been met, see below.

Use of an Assertion Order for the assertions Job Chain

For a given job chain job_chain1 users should create an Assertion Order with an order id like this: !<folder>#job_chain1.

Code Block
languagexml
titleSample Assertion Order for Assertion Job Chain
linenumberstrue
collapsetrue
<order  job_chain="/assertions/assertions">
    <params >
        <param  name="num_of_orders" value="2"/>
    </params>
    <run_time >
        ]]<weekdays >
            <day  </script>
    <run_time />
</job>

Explanations:

day="1 2 3 4 5">
                <period  single_start="18:00"/>
            </day>
        </weekdays>
    </run_time>
</order>


</job>

Explanations:

  • The Assertion Order does not have to use parameters at all: if no parameters are specified then by default all matching Shadow Orders for a given Assertion Order will be removed. 
  • If the Assertion Order makes use of the optional parameter num_of_orders then this parameter specifies the number of Shadow Orders that are expected and that will be removed when the Assertion Order starts. Should a smaller number of Shadow Orders exist than specified by this parameter then the assertion is considered being failed. This parameter is useful if e.g. more than one incoming file is expected from a directory monitored by file watching. 
  • If instead the parameter num_of_successful_orders is used then this signals that Shadow Orders for successfully executed job chains only should be considered. Otherwise successful and failed execution of the originating job chain are both counted as matching events.
  • If the num_of_orders or num_of_successful_orders parameters are used with a value 0 then this causes the default behavior to be applied, i.e. all matching Shadow Orders will be removed.tbd