using System;
using System.Collections.Generic;
using System.Xml;
using System.Collections;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Configuration;
using System.IO;
using Microsoft.Hpc.Scheduler;
namespace LicenseActivationFilterDemo
{
public class RunLicenseFilter
{
public static TextWriter logfile = null;
public static int Main(string[] args) {
try {
// Create a log file by reading a config file that has an attribute called
// "LogFilePath". This attribute contains the path to the log file.
// The value of true that is passed in as the second parameter indicates that
// the log file will be appended to. This is useful for debugging;
// however, you may want to make this parameter false for production use
// so that the file does not become too large.
logfile = new StreamWriter(ConfigurationManager.AppSettings["LogFilePath"],true);
logfile.WriteLine("Starting activation filter");
// When the HPC Job Scheduler runs the activation filter, it adds the name of
// a job XML file to the command line. In this program, this is the first argument.
if (args.Length != 1) {
logfile.WriteLine("Usage: LicenseActivationFilterDemo.exe inputJobTermFile");
return -1;
}
// Utility class to call flexlm tools.
LicenseChecker checker = new LicenseChecker();
// Parse the input job XML file to get the SoftwareLicense job term.
if (!checker.ParseSoftwareLicenseTerms(args[0])) {
logfile.WriteLine("No license required for job exit 0");
return 0;
}
// Check and determine if there are enough free licenses at this moment.
// Note: it is possible that the available licenses may be
// consumed between the time when the activation filter is executed
// and when the task in the job requiring the license is actually started.
if (checker.AreThereEnoughLicenses()) {
return 0;
} else {
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");
return -1;
} finally {
if (logfile != null) {
logfile.Flush();
logfile.Close();
}
}
}
}
public class LicenseChecker {
private Hashtable requestedFeatureTab = new Hashtable();
private Hashtable availableFeatureTab = new Hashtable();
private Hashtable availableFeatureTotals = new Hashtable();
private Int32 jobID = 0;
/// <summary>
/// Read the job XML file and extract the SoftwareLicense job term.
/// </summary>
/// <param name="jobXmlFile">
/// The job file name provided by the HPC Job Scheduler as the first
/// command-line argument when the activation filter is executed.
/// </param>
/// <returns>
/// A value of true is returned if the SoftwareLicense job term is present and was correctly formatted.
/// </returns>
public bool ParseSoftwareLicenseTerms(string jobXmlFile) {
XmlDocument inputJob = new XmlDocument();
inputJob.Load(jobXmlFile);
// The base XML node in the document.
XmlNode job = inputJob.DocumentElement;
// Create the namespace that is used for the job XML schema.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(inputJob.NameTable);
nsmgr.AddNamespace("ab", @"http://schemas.microsoft.com/HPCS2008/scheduler/");
// Get the job ID in case the job needs to be canceled.
XmlNode jobidnode = job.SelectSingleNode(@"@Id", nsmgr);
if (jobidnode != null) {
string JobIdStr = jobidnode.InnerXml;
Int32.TryParse(JobIdStr, out jobID);
} else {
RunLicenseFilter.logfile.WriteLine("Unable to extract jobid from job file");
}
// Get the SoftwareLicense job property.
XmlNode jobnode = job.SelectSingleNode(@"@SoftwareLicense", nsmgr);
if (jobnode == null) {
RunLicenseFilter.logfile.WriteLine("SoftwareLicense attribute not found");
return false;
}
// Extract the value of the SoftwareLicense property.
string licenseStr = jobnode.InnerXml;
if (String.IsNullOrEmpty(licenseStr)) {
RunLicenseFilter.logfile.WriteLine("SoftwareLicense job property is empty");
return false;
}
RunLicenseFilter.logfile.WriteLine("SoftwareLicense job property: " + licenseStr);
// Get all of the license feature:number pairs.
char[] split = { ',' };
string[] tokens = licenseStr.Split(split);
// Loop over all feature:number pairs.
foreach (string str in tokens) {
string[] ftokens = str.Split(':');
if (ftokens.Length == 2) {
Int32 numberOfLicenses = 0;
if (Int32.TryParse(ftokens[1], out numberOfLicenses)) {
requestedFeatureTab[ftokens[0]] = numberOfLicenses;
} else {
RunLicenseFilter.logfile.WriteLine("Error with {0} feature, number of licenses, {1}, not in an integer format",
ftokens[0], ftokens[1]);
}
} else {
RunLicenseFilter.logfile.WriteLine("Error with {0} feature: number of licenses not specified",
ftokens[0]);
}
}
if (requestedFeatureTab.Count <= 0) {
RunLicenseFilter.logfile.WriteLine("No valid feature:number pairs in the SoftwareLicense job property");
return false;
}
return true;
}
/// <summary>
/// Parse the output of the flexlm utility to find all the currently executing license daemons
/// and discover the total available licenses and the total licenses.
/// </summary>
/// <param name="output">
/// The output of the flexlm utility.
/// </param>
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);
Int32 total = 0;
Int32 used = 0;
Int32 value = 0;
total = Int32.TryParse(m.Groups[2].Value, out value) ? value : 0;
used = Int32.TryParse(m.Groups[3].Value, out value) ? value : 0;
availableFeatureTab[m.Groups[1].Value] = total-used;
availableFeatureTotals[m.Groups[1].Value] = total;
}
}
/// <summary>
/// Run the command specified in the config file, PollCommandName, using
/// the arguments, PollCommandArguments.
/// </summary>
public void PollLicenseServer() {
// Run the command and get the exit code.
Process application = new Process();
application.StartInfo.FileName = ConfigurationManager.AppSettings["PollCommandName"];
if (String.IsNullOrEmpty(application.StartInfo.FileName)) {
RunLicenseFilter.logfile.WriteLine("No executable specified in the config file for PollCommandName");
return;
}
application.StartInfo.Arguments = ConfigurationManager.AppSettings["PollCommandArguments"];
if (application.StartInfo.Arguments == null)
application.StartInfo.Arguments = "";
application.StartInfo.RedirectStandardOutput = true;
application.StartInfo.RedirectStandardError = true;
application.StartInfo.UseShellExecute = false;
application.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory();
RunLicenseFilter.logfile.WriteLine("Current working directory is: " + application.StartInfo.WorkingDirectory);
application.Start();
application.WaitForExit();
String stdout = application.StandardOutput.ReadToEnd();
String stderr = application.StandardError.ReadToEnd();
Int32 exitcode = application.ExitCode;
RunLicenseFilter.logfile.WriteLine("Executing: {0} {1}",
ConfigurationManager.AppSettings["PollCommandName"],
ConfigurationManager.AppSettings["PollCommandArguments"]);
RunLicenseFilter.logfile.WriteLine("Start Time: {0}",application.StartTime.ToLocalTime().ToLongTimeString());
RunLicenseFilter.logfile.WriteLine("End Time : {0}",application.ExitTime.ToLocalTime().ToLongTimeString());
RunLicenseFilter.logfile.WriteLine("Exitcode: {0}",exitcode);
RunLicenseFilter.logfile.WriteLine("Stdout : {0}", stdout);
RunLicenseFilter.logfile.WriteLine("Stderr : {0}", stderr);
// Add the returned values from flexlm to the availableFeatureTab HashTable.
ParseFlexlmOutput(stdout);
}
/// <summary>
/// Iterate over all the features to make sure that there are available
/// licenses at this moment.
/// </summary>
/// <returns>
/// A value of true is returned if there are licenses available;
/// a value of false is returned if there are not enough or if a specified feature does not exist.
/// </returns>
public bool AreThereEnoughLicenses() {
Boolean enoughLicenses = true;
//Execute the command that is specified in the config file and parses the results.
PollLicenseServer();
// Iterate through the requesting features and determine whether
// there are enough license tokens.
IDictionaryEnumerator iter = requestedFeatureTab.GetEnumerator();
// Run lmutils lmstat to verify the available license tokens.
while (iter.MoveNext()) {
bool jobCanceled = false;
RunLicenseFilter.logfile.WriteLine("Checking " + iter.Key + " " + "{0}", iter.Value);
if (!AreThereEnoughFeatureXTokens((string)iter.Key, (int)iter.Value, out jobCanceled)) {
enoughLicenses = false;
}
// The job was canceled. Return a value of true so that the activation
// filter will not block the job queue.
if( jobCanceled ) {
return true;
}
}
return enoughLicenses;
}
/// <summary>
/// Check to see whether there are licenses available for the requested features.
/// </summary>
/// <param name="featureName">
/// The feature name to check for.
/// </param>
/// <param name="requestedAmt">
/// The number of licenses required.
/// </param>
/// <param name="jobCanceled">
/// If true, then the job was canceled.
/// </param>
/// <returns>
/// A value of false is returned if either the feature does not exist or if there are not enough licenses.
/// </returns>
private bool AreThereEnoughFeatureXTokens(string featureName, int requestedAmt, out Boolean jobCanceled) {
jobCanceled = false;
if (!availableFeatureTab.Contains(featureName)) {
// The job will be canceled if the feature is unknown.
// This is because the activation filter will block the
// queue if a non-zero exit code is returned, and submit the job if a zero exit code is returned.
// When the job is canceled, the filter
// will return a value of 0, and it will not block the queue.
String message = "Canceled by activation filter: Unknown License feature requested: "+ featureName;
this.CancelJob(message);
jobCanceled = true;
return true;
}
int availableAmt = (int)availableFeatureTab[featureName];
int totalAmt = (int)availableFeatureTotals[featureName];
if( requestedAmt>totalAmt ) {
// The job will be canceled if the licenses requested are more than the maximum
// allowable for this feature. This is because the activation filter will block the
// queue if a non-zero exit code is returned, and submit the job if a zero exit code is returned.
// Since the number of license requests will never be satisfied, the job is canceled. The
// filter will return a value of 0 and it will not block the queue.
String message = "Canceled by activation filter: Requested amount of licenses,"+requestedAmt+", for the feature,"+featureName+" ,greater than maximum licenses allowed," + totalAmt;
this.CancelJob(message);
jobCanceled = true;
return true;
} else if (availableAmt < requestedAmt) {
RunLicenseFilter.logfile.WriteLine("Not enough licenses for " + featureName + ": requested: " + requestedAmt + " available: "
+ availableAmt);
return false;
} else {
RunLicenseFilter.logfile.WriteLine("Enough licenses for " + featureName + ": requested: " + requestedAmt + " available: "
+ availableAmt);
return true;
}
}
/// <summary>
/// Cancel a job with the supplied message.
/// </summary>
/// <param name="message">
/// The message to attach to the canceled job.
/// </param>
private void CancelJob(String message) {
RunLicenseFilter.logfile.WriteLine(message + ": jobid, " + jobID + ", being canceled");
RunLicenseFilter.logfile.Flush();
IScheduler scheduler = new Scheduler();
scheduler.Connect("localhost");
scheduler.CancelJob(jobID, message);
scheduler.Dispose();
}
}
}