Adding Job Submission and Activation Filters

Updated: June 6, 2006

Applies To: Windows Compute Cluster Server 2003

You can enforce site-specific job submission policies and job activation policies by creating custom filters. These are referred to as job submission filters and job activation filters.

Job submission filters

A job submission filter parses the job template file to check for options that are disallowed or for failure to include a required option. For example, you might want to disallow the default Run Time value of Infinite. This could be done by a submission filter that checks the template file for "Infinite," and, if it finds it, changes it to a finite value.

noteNote
Submission filters allow you to change job property values only. Task property options cannot be changed using a submission filter.

As another example, you might want to require a new job option that was created by editing the job schema. These are called extended options and they are not checked by the scheduler. Therefore, a submission filter would be necessary to enforce the requirement and to prevent a job from entering the queue if the option is not found.

Job activation filters

A job activation filter checks a queued job for factors that would cause the job to fail if activated, such as unavailability of licenses or exceeded usage time for the submitting user. If the filter detects the condition, it prevents the job from activating.

Required filter exit codes

Reflecting the difference between their functionalities, a submission filter and an activation filter have different required exit codes. These are shown in the following table:

 

Submission Filter Exit Codes

0=OK; 1=Job template required correction before being submitted. Any other exit code=job was not submitted.

Activation Filter Exit Codes

0=OK; Any other exit code=did not activate, job remains in the job queue.

Creating and installing a filter

Creating and installing a submission or activation filter plug-in consists of the following general steps:

  1. Write and compile the plug-in code in any language desired. (As a plug-in, it is read by the scheduler in executable form.)

  2. Install the plug-in on the head node.

  3. For a submission filter, set the clusterwide parameter SubmissionFilterProgram to the plug-in path. (cluscfg setparams SubmissionFilterProgram<path>.)
    Then set the clusterwide parameter SubmissionFilterTimeout to the desired plug-in timeout value. (cluscfg setparams SubmissionFilterProgram<minutes>.)

  4. For an activation filter, set the clusterwide parameter ActivationFilterProgram to the plug-in path. (cluscfg setparams ActivationFilterProgram<path>.)
    Then set the clusterwide parameter ActivationFilterTimeout to the desired plug-in timeout value. (cluscfg setparams ActivationFilterProgram<minutes>.)

Example:An activation filter for license-aware scheduling

The following example provides general instructions for creating an activation filter for license aware scheduling and adding it as a plug-in to the scheduler, to be invoked whenever a job is submitted. Sample code in C# is included.

The admissions filter is named LicenseActivationFilterDemo.exe and does the following:

  • Searches the job xml file for values pairs (feature and number) assigned to the SoftwareLicense property.

  • If a null value is found (meaning that the job requires no licenses), exits with a zero exit code, allowing the job to be activated.

If a non-null value is found, searches for the license features in a specified location. If the required license features and number of each are found, the plug-in exits with a zero exit code, allowing the job to be activated. If the license features and required number of each are not found, the plug-in exits with a non-zero exit code, and the job is not activated.

To build, install, and run the license-aware activation filter

  1. Place the C# compiler executable (Csc.exe) in your search path as follows:

    1. Right-click My Computer and select Properties.

    2. Click the Advanced tab.

    3. Click the Environment Variables button.

    4. In the System Variables pane, double-click Path.

    5. Add the following path: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727.

  2. Create the folder C:\ActivationFilter

  3. In Notepad, paste the sample C# code (shown later in this document) (LicenseActivationFilterDemo source code (C#)) and save it as C:\ActivationFilter\LicenseActivationFilterDemo.cs

  4. Compile the code as follows:

    csc LicenseActivationFilterDemo.cs

  5. Copy the XML configuration file (shown later in this document) to C:\ActivationFilter\ LicenseActivationFilterDemo.exe.config.

  6. Edit LicenseActivationFilterDemo.exe.config, replacing the lmutil command line and the license file License.lic with your license file utility command line for obtaining license status.

  7. Copy your utility executable and license file to C:\ActivationFilter.

  8. Set the activation plug-in parameter to the executable filter program:

    cluscfg setparams activationfilterprogram=C:\ActivationFilter \LicenseActivationFilterDemo.exe

  9. Specify the license feature and the number of tokens in your job. For example:

    job submit/numprocessors:4 /lic:myfeature:4.myapp.exe.

  10. After the job is finished, review C:\LicenseActivationFilterDemo.log.txt for details.

LicenseActivationFilterDemo source code (C#)

The following code in C# implements the activation filter described previously.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Collections;
using System.Diagnostics;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Configuration;
using System.IO;

namespace LicenseActivationFilterDemo
{
    public class RunLicenseFilter
    {
        public static TextWriter logfile = null; 

        public static int Main(string[] args)
        {
            try {
                logfile = new StreamWriter(ConfigurationSettings.AppSettings["LogFilePath"]);
                if (args.Length != 1)
                {
                    logfile.WriteLine("Usage: LicenseActivationFilterDemo.exe inputJobTermFile");
                    logfile.Close();
                    return -1;
                }
                
                
      
                LicenseChecker checker = new LicenseChecker();

                if (! checker.ParseSoftwareLicenseTerms(args[0]))
                {
                    logfile.WriteLine("No license required for job exit 0");
                    logfile.Close();
                    return 0;            
                }

                if (checker.IsThereEnoughLicenses())
                {
                    logfile.Close();
                    return 0;
                }
                else
                {
                    logfile.Close();
                    return 1;
                }
            }
            catch (Exception ex)
            {
                logfile.WriteLine("MESSAGE: " + ex.Message);
                logfile.WriteLine("SOURCE: " + ex.Source);
                logfile.WriteLine("TARGET: " + ex.TargetSite);
                logfile.WriteLine("STACK: " + ex.StackTrace + "\r\n");
                logfile.Close();

                return -1;
            }
        }
    }

    public class LicenseChecker
    {
        private Hashtable requestedFeatureTab = new Hashtable();
        private Hashtable availableFeatureTab = new Hashtable();

        

        public bool ParseSoftwareLicenseTerms(string jobXmlFile)
        {
            XmlDocument inputJob = new XmlDocument();

            inputJob.Load(jobXmlFile);
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(inputJob.NameTable);
            nsmgr.AddNamespace("ab", "http://www.microsoft.com/ComputeCluster/");

            XmlNodeList xmlNodes = inputJob.SelectNodes("//Job/@SoftwareLicense", nsmgr);
            XmlAttribute licenseAttr = (XmlAttribute)xmlNodes.Item(0);

            if (licenseAttr == null)
                return false;

            if (licenseAttr.Value.Equals(""))
            {
                return false;
            }

            RunLicenseFilter.logfile.WriteLine(licenseAttr.Name + " " + licenseAttr.Value);

            // Tokenlize each license feature
            string licenseStr = licenseAttr.Value;
            string[] tokens = licenseStr.Split(',');

            foreach (string str in tokens)
            {
                string[] ftokens = str.Split(':');
                requestedFeatureTab[ftokens[0]] = Int32.Parse(ftokens[1]);
            }

            return true;
        }

        public void TestFlexlmParser()
        {
            string source = @"Users of ansys: (Total of 123 licenses issued;   Total of 12 licenses in use)
                              Users of ansyssolver: (Total of 32 licenses issued;  Total of 2 licenses in use)
                              Users of ansysclient: (Total of 34 licenses issues;  Total of 34 licenses in use)";

            ParseFlexlmOutput(source);
        }

        public void ParseFlexlmOutput(string output)
        {
            string matchPattern = @"Users of (\w+):.*Total of (\d+) licenses issued;.*Total of (\d+) licenses in use";

            MatchCollection theMatches = Regex.Matches(output, matchPattern);

            foreach (Match m in theMatches)
            {
                RunLicenseFilter.logfile.WriteLine(m.Groups[1].Value + "=" + m.Groups[2].Value + " " + m.Groups[3].Value);

                availableFeatureTab[m.Groups[1].Value] = Int32.Parse(m.Groups[2].Value) - Int32.Parse(m.Groups[3].Value);
            }
        }

        public void PollLicenseServer()
        {
            // Run the command and get the exit code
            Process application = new Process();

            application.StartInfo.FileName = ConfigurationSettings.AppSettings["PollCommandName"];
            application.StartInfo.Arguments = ConfigurationSettings.AppSettings["PollCommandArguments"];
            application.StartInfo.RedirectStandardOutput = true;
            application.StartInfo.UseShellExecute = false;
            application.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory();
            RunLicenseFilter.logfile.WriteLine("Current working directory is: " + application.StartInfo.WorkingDirectory);

            application.Start();

            string output = application.StandardOutput.ReadToEnd();

            RunLicenseFilter.logfile.WriteLine("Output of lmutils is");
            RunLicenseFilter.logfile.WriteLine(output + "\r\n");

            application.WaitForExit();

            ParseFlexlmOutput(output);
        }

        public bool IsThereEnoughLicenses()
        {
            //ParseSoftwareLicenseTerms(jobXmlFile);
            PollLicenseServer();
            //
            // Iterate through the requesting features and see whether
            // there are enough license tokens
            //
            IDictionaryEnumerator iter = requestedFeatureTab.GetEnumerator();

            // Run lmutils lmstat to verify the available license tokens
            while (iter.MoveNext())
            {
                RunLicenseFilter.logfile.WriteLine("Checking " + iter.Key + " " + "{0}", iter.Value);

                if (! IsThereEnoughFeatureXTokens((string) iter.Key, (int) iter.Value))
                {    
                    return false;
                }
            }

            return true;
        }

        private bool IsThereEnoughFeatureXTokens(string featureName, int requestedAmt)
        {
            if (!availableFeatureTab.Contains(featureName))
            {
                RunLicenseFilter.logfile.WriteLine("Unknown License feature requested: " + featureName);
                return false;
            }
            int availableAmt = (int)availableFeatureTab[featureName];

            if (availableAmt < requestedAmt)
            {
                RunLicenseFilter.logfile.WriteLine("Not enough licenses for " + featureName + ": requested: " + requestedAmt + " available: "
                + availableAmt);
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}

LicenseActivationFilterDemo configuration file

The following XML configuration file assigns values to the program parameters at run time. The configuration file should be placed in the same folder as the executable filter.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
    <add key="PollCommandName" value="c:\ActivationFilter\lmutil.exe" />
    <add key="PollCommandArguments" value="lmstat -c c:\ActivationFilter\License.lic -a" />
    <add key="LogFilePath" value="C:\ActivationFilter\LicenseActivationFilterDemo.log.txt" />
</appSettings>
</configuration>.

Community Additions

ADD
Show: