You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

Scenario

  • Frequently the fact that a job chain is executed depends on an external event, e.g. when watching incoming files from a monitored directory. This situation is non-deterministic, i.e. we cannot know if a file will arrive or not. However, from a business perspective we assume a file to arrive, e.g. Mon-Fri not later than 18:00.
  • This article suggests a solution introducing Assertions that manage the expectation what should happen and when it should happen.

Strategy

  • Basically the solution should work for all job chains, including job chains that are started from file orders and job chains that are e.g. manually started by ad hoc orders. This solution is not intended for standalone jobs.
  • For a job chain that is expected to execute until a given date and time a shadow order is created that implements the assertion.
    • The shadow order is assigned a start-time rule or calendar based rule to start e.g. Mon-Fri at 18:00. This is the point in time when the expectation should be met, i.e. at the given point in time the shadow order checks if the respective job chain has been executed.
    • Should this check be successful, i.e. the job chain has been executed then the shadow order will complete its run successfully and will recalculate its next start-time.
    • Should this check fail, i.e. the job chain has not been executed then the shadow order will fail and create a notification to signal a failed assertion.
  • In addition to expecting a single execution of a job chain the solution should include
    • to check if more than one execution of a job chain occurred.
    • to check e.g. successful executions of a job chain only.

Implementation

  • 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.

Assertion Job Chain

Assertion Job Chain
<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:

  • tbd

Assertion Job

Assertion Job
<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' ) )
    {
        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 )
    {
        spooler_log.error( 'wrong format for order id, use: !folder[!sub-folder]#name[#order_id]: ' + spooler_task.order.id );
        return false;
    } else {
        jobChainFolder = parts[0];
        jobChainName = parts[1];

        if ( parts.length > 2 )
        {
            orderId = parts[2];
        }
    }

    // handle asserting order
    if ( !orderId )
    {
        // lookup resolving orders
        var command = "<show_state subsystems='folder order' what='folders job_chain_orders no_subfolders' path='" + spooler_task.job.folder_path + "'/>";
        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 );
        // traverse node list
        for ( var xmlIndex=0; xmlIndex < xmlNodes.getLength(); xmlIndex++ )
        {
            var xmlNode = xmlNodes.item( xmlIndex );
            var xmlOrderId = xmlResponse.selectSingleNodeValue( xmlNode, "@id" );

            if ( xmlOrderId == null )
            {
                  continue;
            }

            if ( numOfSuccessfulfOrders > 0 )
            {
                var xmlOrderTitle = xmlResponse.selectSingleNodeValue( xmlNode, "@title" );
                if ( xmlOrderTitle.startsWith( 'failed:' ) )
                {
                    spooler_log.info( '.. failed resolving order found: ' + xmlOrderId );
                    continue;
                }
            }

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

        // default: remove all resolving orders for an asserting order
        if ( numOfOrders == 0 )
        {
            spooler_log.info( '.. all resolving orders will be removed' );
            for( var i=0; i<candidateOrders.length; i++ )
            {
                spooler_log.info( '.. resolving suspended order is removed: ' + candidateOrders[i] );
                command = "<remove_order job_chain='" + spooler_task.order.job_chain.path + "' order='" + candidateOrders[i] + "'/>";
                executeXml( command );
            }

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

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

    return true;
}

function executeXml( command ) {
    var rc = false;

    spooler_log.debug( '.... executing xml command: ' + command );
    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;
}
        ]]>
    </script>
    <run_time />
</job>

Explanations:

  • tbd



  • No labels