Name

global.DiscoveryCimClassificationSensor

Description

Classifies systems based on results returned from a CIM classification probe.

Script

/** 
* Classifies systems based on results returned from a CIM classification probe.
* @author roy.laurie
*/
var DiscoveryCimClassificationSensor = Class.create();

DiscoveryCimClassificationSensor.prototype = Object.extendsObject(DiscoverySensor, {
  prepare: function() {
  	try {
         if (g_device === null || g_device.getCmdbCi())
  	       return;

  		// Trigger any additional classifiers against g_device.  The server will
  		// remain in "classifying" if I don't do this.
  		this.fireAdditionalClassifier();
      } catch (e) {
          this.processError(''+e);
  	}
  },
 
  /**
   * @override
   * @param XMLObj probeResult
   */
  process: function(probeResult) {
  	try {
          this._initializeCimSensor(probeResult);
          this._processClassifier();
  	} catch (e) {
          this.processError(''+e);
  	}
  },
  
  /**
   * @param XMLObj probeResult
   */
  _initializeCimSensor: function(probeResult) {
      this._cimQueries = new CimProbeResult(probeResult).getQueries();
  	this._cimInstances = [];
  },	
  
  _processClassifier: function() {
  	var classificationRecords = this._getCimClassificationRecords();
  	var i = 1;
  	while (classificationRecords.next()) {
  		// skip disabled classifiers
  		if (!classificationRecords.disabled)
  			this._processClassification(classificationRecords);
  	}
  },

  /**
   * @return GlideRecord(discovery_classy_cim) All active CIM classifications.
   */
  _getCimClassificationRecords: function() {
  	var classifications = new GlideRecord('discovery_classy_cim');
  	classifications.addActiveQuery();
  	classifications.query();
  	return classifications;
  },
  
  /**
   * @param GlideRecord(discovery_class_cim) cimClassification
   * @return GlideRecord(discovery_class_criteria) All active Criteria for the specified CIM Classification.
   */
  _getCriteriaRecords: function(cimClassification) {
  	var criteria = new GlideRecord('discovery_class_criteria');
  	criteria.addActiveQuery();
  	criteria.addQuery('classy', ''+cimClassification.sys_id);
  	criteria.query();
  	return criteria;
  },	
  
  /**
   * @param GlideRecord(discovery_classy_cim) classificationRecord
   * @throws AutomationException On error from Classification Script
   */
  _processClassification: function(classificationRecord) {
  	var cimCriteriaQueryMap = this._mapCriteriaToCimQueries(classificationRecord);
  	var script = ''+classificationRecord.script;
  	var table = ''+classificationRecord.table;
  	var instanceInputs = null;
  	if (!gs.nil(script)) {
  		try {
  			instanceInputs = this._runOnClassificationScript(script, cimCriteriaQueryMap, table);
  		} catch (e) {
  			gs.log(e.toString());
  			throw new AutomationException('Error in onClassification script for Classification `' + classificationRecord.name + '`. ' + e);
  		}
  	} else {
  		instanceInputs = this._getAllCriteriaInstanceInputs(cimCriteriaQueryMap, table);
  	}
  		
  	var deviceHistory = this.getDeviceHistory();
  	var deviceHistorySysId = deviceHistory.getDeviceRecord();
  	deviceHistorySysId = deviceHistorySysId && ('' + deviceHistorySysId.sys_id);

  	for (var i = 0, n = instanceInputs.length; i < n; ++i) {
  		var input = instanceInputs[i];
  		
  		// discoveryData.freezeDeviceHistory can be used to maintain the current IP-based Discovery Status Source and Device History
  		if (!input.discoveryData.freezeDeviceHistory)
  			// create a new device history for this "representation" of a device
  			this._createCimDeviceHistory(classificationRecord, input.instance, deviceHistorySysId);
  	}
     
  	this._fireCimProbes(classificationRecord, instanceInputs);
  	
  	if (!this.getParameter('is_supplementary')) {
  		var ipsa = new IPServiceAffinity(this.getSource(), this.getParameter('port'));		
  		ipsa.setIPServiceAffinity();
  	}
  },
  
  /**
   * @return DeviceHistory
   * @throws DiscoveryException
   */
  _createCimDeviceHistory: function(classificationRecord, instanceToken, deviceHistorySysId) {
  	var source = new CimInstanceToken(instanceToken).getHashToken(this.getSource());
  	var deviceHistory = SncDeviceHistory.createDeviceHistory(source, null, g_probe.getEccQueueId(), g_probe.getParameter('classification_probe'));
  	if (deviceHistory === null)	
  		throw new DiscoveryException('Unable to determine current Device History for instance `' + source + '`.');

      deviceHistory.setScratchpadValue('sourceIP', this.getSource());
  	deviceHistory.setScratchpadValue('cimInstance', instanceToken);
  	deviceHistory.setScratchpadValue('parentDeviceHistorySysId', deviceHistorySysId);
  	
      deviceHistory.setClassifiedAs(''+classificationRecord.table);
  	return deviceHistory;
  },
 
  /**
   * @param GlideRecord(discovery_classy_cim) classificationRecord
   * @return { criteria.name : XMLObj(//cimqueryset/cimquery},.. }
   * @throws IllegalArgumentException If any criterion cannot be mapped.
   */
  _mapCriteriaToCimQueries: function(classificationRecord) {
  	var classificationCimQueries = {};
  	var criteriaRecords = this._getCriteriaRecords(classificationRecord);
  	while (criteriaRecords.next()) {
  		var name = ''+criteriaRecords.name;
  		var cimQuery = this._findCimQuery(''+criteriaRecords.criterion, this._cimQueries);

  		classificationCimQueries[name] = cimQuery;
  	}

  	return classificationCimQueries;
  },		

  /**
   * @param string query
   * @param XMLObj(//cimqueryset/cimquery[]) classificationCimQuery
   * @return null|XMLObj(//cimqueryset/cimquery)
   */
  _findCimQuery: function(query, classificationCimQuery) {
  	for (var i = 0, n = classificationCimQuery.length; i < n; ++i) {
  		if (classificationCimQuery[i].query == query)
  			return classificationCimQuery[i];
  	}
  	
  	return null;
  },

  /*
   *
   */
  _findCimQueryByName: function(name) {
  	var i,
  		queries = this._cimQueries;

  	for (i = 0; i < queries.length; i++) {
  		if (queries[i].name == name)
  			return queries[i];
  	}
  },

  /************************************************************************************************************************
   * Validates a single classification criterion result. Processed against all criteria results one by one.
   * @param string name The Classification Criteria Name of the query.
   * @param XMLObj(//cimqueryset/cimquery/result/instance) instance The instance returned. Valid. Format:
   *   {_key: {string:string,..}, _namespace: string, _classname: string, _cidata: {string:string,..}, {MyProp:string},..}
   * @param CIData cidata The CIData for this instance. Pre-set with 'sys_class_name'.
   * @param { criteria.name: XMLObj(//cimqueryset/cimquery/result/instance[]),.. } results 
   * @param Object uniqueInstance   A javascript object that will be passed to all results for the same instance.
   *                                When there are multiple queries, the classification script can be called more than
   *                                once for the same object, e.g. a storage server that supports both the NAS and
   *                                Array profiles.  When this happens the script will be called more than once, but
   *                                this parameter will be the same for all calls.
   * @return true|false|XMLObj(//cimqueryset/cimquery/result/instance) Return the instance if approved, false to skip,
   *                                                                   true to approve the uniqueInstance parameter (you
   *                                                                   can approve the uniqueInstance any number of times
   *                                                                   and it will only be explored once.)
   *
   * Operating on the uniqueInstance and returning true or false is the best practice.  The "instance" parameter is retained
   * only for backward compability.
   */ /*
  function classify(name, instance, cidata, results, uniqueInstance) {
  	return true;
  } */
  
  /**
   * Executes the associated CIM Classification Filter Function defined as demonstrated above.
   * @param string script The script body to run
   * @param { criteria.name : XMLObj(//cimqueryset/cimquery),.. } cimCriteriaQueryMap
   * @param string table
   * @return [{ namespace: string, instance: string, cidata: {string:string,..} }
   */
  _runOnClassificationScript: function(script, cimQueries, table) {
  	var name, name2, i, instances, instance, instanceInput, result, id, communicationMechanism,
  		multipleOperationsSupported = false,
  		onClassificationFunction = new Function('name', 'instance', 'queries', 'uniqueInstance', 'return (' + script + '(name, instance, queries, uniqueInstance));'),
  		instanceMap = { },
  		instanceInputs = [];

  	try {
  		communicationMechanism = this._findCimQueryByName('XMLCommunicationMechanism');
  		multipleOperationsSupported = communicationMechanism.result.instance[0].MultipleOperationsSupported.toLowerCase() == 'true';
  	} catch (e) { }

  	for (name in cimQueries) {
  		instances = cimQueries[name] && cimQueries[name].result.instance;
  		if (!instances)
  			continue;

  		for (i = 0, n = instances.length; i < n; ++i) {

  			instance = instances[i];

  			id = instance._path + '**' + instance.ElementName;

  			if (!instanceMap[id]) {
  				instanceInput = {
  					namespace: instance._namespace,
  					instance: this._cimInstanceToQuery(instance),
  					multipleOperationsSupported: multipleOperationsSupported,
  					cidata: new CIData(),
  					_discoveryData: { }
  				};

  				for (name2 in instance)
  					instanceInput._discoveryData[name2] = instance[name2];

  				instanceMap[id] = instanceInput;
  				instanceInput.cidata.data.sys_class_name = table;
  				instanceInput.discoveryData = instanceInput._discoveryData;
  			}

  			instanceInput = instanceMap[id];

  			instance._cidata = instanceInput.cidata;
  			instance._discoveryData = instanceInput._discoveryData;

  			result = onClassificationFunction(name, instance, cimQueries, instanceInput);
  			
  			if (result) {
  				if (result === true) {
  					if (!instanceInput._pushed) {
  						instanceInput._pushed = true;
  						instanceInputs.push(instanceInput);
  					}
  				}
  				else
  					instanceInputs.push(result);
  			}
  		}
  	}

  	return instanceInputs;
  },
  
  /**
   * @param XMLObj(//cimqueryset/cimquery/result/instance) cimInstance
   * @return string e.g., CIM_ClassName{Key1='Value1', Key2='Value2'}
   */
  _cimInstanceToQuery: function(cimInstance) {
  	var identity = '';
  	for (var name in cimInstance._key)
  		identity += name + "='" + cimInstance._key[name] + "',";
  	
  	identity = identity.substr(0, identity.length - 1); // trim trailing ,
  	var query = cimInstance._classname + '{' + identity + '}';
  	return query;
  },
  
  /**
   * Returns CIM Probe Instance Inputs for all results, without filter. Used when no onClassification script
   * is specified.
   * @param { string : XMLObj(//cimqueryset/cimquery},..}
  * @return [{ namespace: string, instance: string, cidata: {string:string,..} }]
   */
  _getAllCriteriaInstanceInputs: function(cimCriteriaQueryMap, table) {
  	var instanceInputs = []; // use all instance results from the query map
  	for (var name in cimCriteriaQueryMap) {
  		var instance = cimCriteriaQueryMap[name].result.instance;
  		var input = {
  			namespace: instance._namespace,
  			instance: this._cimInstanceToQuery(instance),
  			cidata: new CIData()
  		};
  		
  		input.cidata.data.sys_class_name = table;
  		instanceInputs.push(input);
  	}
  	
  	return instanceInputs;
  },
  
  /**
   * @param GlideRecord(discovery_classy_cim) classificationRecord
   * @param { namespace: string, instance: string, cidata: {string:string,..} }[] instanceInputs
   */
  _fireCimProbes: function(classificationRecord, instanceInputs) {
  	var parameters = {
  		source: this.getSource(),
  		port: ''+this.getParameter('port'),
  		classifier: ''+classificationRecord.sys_id,
  		port_probe: ''+this.getParameter('port_probe'),
  		ecc_breadcrumbs: ''+this.getEccQueueId(),
  		triggered_probes: null,
  		namespace: null,
  		instance: null,
  		device_instance: null,
  		cidata: null
  	};
  	
  	var classer = new DiscoveryClassification();
  	var pattern = classer.getPaternNameFromProbe(true, classificationRecord.child, classificationRecord.classy);
  	
  	if (pattern) {
  		parameters.pattern = pattern;
  		
  		var affinity = classer.getCredentialAffinity(this.getSource());
  		if (affinity)
  			parameters.affinity = affinity;
  			
			_fireCimProbe(classifier, parameters);
  		return;
  	}
  	
  	var identityProbes = this.getTriggeredProbes(classificationRecord, 'Identification');
  	var explorationProbes = this.getTriggeredProbes(classificationRecord, 'Exploration');
  	
  	for (var ip = 0; ip < identityProbes.length; ++ip) {
  		for (var ii = 0; ii < instanceInputs.length; ++ii) {
  			var instanceInput = instanceInputs[ii];

  			// validate triggered probe conditions for this instance
  			var explorationProbeIds = this.validateTriggeredProbeConditions(explorationProbes, instanceInput.discoveryData);
  			
  			// configure probe parameters
  			parameters.namespace = instanceInput.namespace;
  			parameters.instance = instanceInput.instance;
  			parameters.device_instance = instanceInput.instance;
  			parameters.cidata = instanceInput.cidata.toXML();
  			parameters.triggered_probes = explorationProbeIds.join(',');
  			parameters.multipleOperationsSupported = instanceInput.multipleOperationsSupported || false;
  			
  			// if discoveryData.freezeDeviceHistory wasn't set, trace the parent device history
  			if (!instanceInput.discoveryData.freezeDeviceHistory)
  				parameters.relate_discovered_to_discovers = 'true';

  			var identityProbeIds = this.validateTriggeredProbeConditions([ identityProbes[ip] ], instanceInput.discoveryData);
  			if (identityProbeIds.length)
  				this._fireCimProbe(identityProbeIds[0], parameters);
  		}
  	}
  },
  
  /**
   * Enqueues a CIM Probe run.
   * @param string id
   * @param {string:string,..} parameters
   */
  _fireCimProbe: function(id, parameters) {
  	var probe = Probe.getById(id);
  	if (!probe)
  		throw new IllegalArgumentException('Probe `' + id + '` not found.');
  	
  	probe.setSource(this.getParameter('source'));
  	probe.setEccPriority(this.getParameter('priority'));
  	probe.setMidSelectDetails(this.getParameter('mid_selector_details'));
  	
  	for (var name in parameters)
  		probe.addParameter(name, parameters[name]);
  		
  	probe.create(this.getAgent(), this.getEccQueueId());
  },
  
  /**
   * Retrieves the triggered probe IDs for the given classifier and phase.
   * Validates conditions scripts.
   * @param GlideRecord(discovery_classy_cim)
   * @param string phase (Optional)
   * @return {}[] JS obj of discovery_classifier_probe
   */
  getTriggeredProbes: function(classificationRecord, phase) {
  	var m2m = new GlideRecord('discovery_classifier_probe');
  	m2m.addQuery('classy', ''+classificationRecord.sys_id);
  	if (!gs.nil(phase))
  		m2m.addQuery('phase', phase);

  	m2m.addActiveQuery();
  	m2m.query();
  	var triggeredProbes = [];
  	while (m2m.next()) {
  		
  		triggeredProbes.push({
  			sys_id: ''+m2m.child,
  			condition_script: ( gs.nil(m2m.condition_script) ? null : ''+m2m.condition_script ),
  		});
  	}

  	return triggeredProbes;
  },
  
  validateTriggeredProbeConditions: function(triggeredProbes, discoveryData) {
  	var passedProbes = [];
  	for (var i = 0; i < triggeredProbes.length; ++i) {
  		var probe = triggeredProbes[i];
  		
  		if (probe.condition_script === null) {
  			passedProbes.push(probe.sys_id);
  			continue;
  		}
  		
  		// pass discoveryData twice; it will be re-used as values
  		var funcstr = 'return ( function validate(discoveryData, values) { return ( ' + probe.condition_script + ' ); } )(discoveryData, values);';
  		var conditionFunc = new Function('discoveryData', 'values', funcstr);
  		try {
  			if (conditionFunc.call(discoveryData, discoveryData) === true)  {
  				passedProbes.push(probe.sys_id);
  				passed = true;
  			}
  		} catch (e) {
  			gs.logError('Error in classification probe conditional script: ' + e);
  		}
  	}
  	
  	return passedProbes;
  },

  type: 'DiscoveryCimClassificationSensor'
});

Sys ID

b49d5e1337622100dcd445cbbebe5d27

Offical Documentation

Official Docs: