Name

global.CimSensor

Description

A DiscoverySensor for CIM Probe results.

Script

// Discovery
/**
* @var DiscoverySensor CimSensor
* @author Roy Laurie <roy.laurie@service-now.com>
* @abstract
*/
var CimSensor = Class.create();

CimSensor.PARAMETER_PROBE = 'probe';
CimSensor.PARAMETER_DEVICE_INSTANCE = 'device_instance';
CimSensor.PARAMETER_INSTANCE = 'instance';
CimSensor.PARAMETER_INSTANCE_ID = 'instance_id';
 
CimSensor.Relation = Class.create();
CimSensor.Relation.prototype = {
  initialize: function(fromId, nature, toTable, keys, options) {
  	this.fromId = fromId;
  	this.toTable = toTable;
  	this.keys = keys;
  	this.cidata = [];
  	this.nature = nature;
  	this.options = {
  		retireMissing: false,
  		direction: 'upstream'
  	};
  	
  	if (typeof options.retireMissing !== 'undefined')
  		this.options.retireMissing = ( options.retireMissing === true );
  	if (typeof options.direction !== 'undefined')
  		this.options.direction = ( options.direction === 'downstream' ? 'downstream' : 'upstream' );
  }
};

CimSensor.prototype = Object.extendsObject(DiscoverySensor, {
  init: function(definition) {
  	DiscoverySensor.prototype.init.call(this, definition);
  	g_device = this.getDeviceHistoryGlobal();
  },
  
  /**
   * CIM device history uses a hash of the device's CIMQL identity string and uses that
   * as the device history source rather than IP, which is used in normal discoveries.
   * IPs cannot be used here uniquely, as CIM probes query the CIMOM - or CIM Server - which may not
   * necessarily be the same physical device as
   * the one we're discovering.
   * @override DiscoverySensor.prototype.getDeviceHistory
   * @return DeviceHistoryJS
   */
  getDeviceHistory: function() {
  	var source = this.getInstanceHashToken();
  	var status = this.getAgentCorrelator();
  	var classificationProbe = g_probe.getParameter('classification_probe');
  	var deviceHistory = new DeviceHistoryJS();
  	deviceHistory.setSource(source);
  	deviceHistory.setStatus(status);
  	deviceHistory.setClassificationProbe(classificationProbe);
  	return deviceHistory;
  },
  
  getDeviceHistoryGlobal: function() {
  	var source = this.getInstanceHashToken();
  	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 + '`.');

  	return deviceHistory;
  },
  
  getInstanceHashToken: function() {
  	var instanceToken = new CimInstanceToken(''+this.getParameter(CimSensor.PARAMETER_DEVICE_INSTANCE));
  	var hashToken = instanceToken.getHashToken(''+this.getSource());
  	return hashToken;
  },	
  
  /**
   * @override
   */
  prepare: function() {
  	/* @var { name: XMLObj(//cimqueryset/result/instance) } */
  	this._queryResults = null;
  	/* @var { probe key: {instance: XMLObj(), cidata: CIData} } */
  	this._triggeredBatches = {};
  	/* @var { probe key: {id:string} } */
  	this._triggeredProbes = {};
  	/* @var { name: CimSensor.Relation } */
  	this._related = {};
  },
  
  /**
   * Processes CIM Probe query results and updates ci/gr data where needed.
   * @override
   * @param { name: XMLObj(//cimquery/result/instance)[],.. } results
   * @param string instanceId The ID of the probed CIM instance.
   * @param string deviceId The ID of the classified device.
   * @param string instanceToken The probed instance's CIM query token.
   * @param string deviceToken The classified device's CIM query token.
   * @param {} current Updates the g_device with injected fields at end of probe cycle.
   * @throws DiscoveryException On error
   */
  processCimLegacy: function(results, instanceId, deviceId, instanceToken, deviceToken, current) {
  	throw new DiscoveryException('CimSensor.processCim() needs to be overridden in sensor `' + this.getSensorName() + '`.');
  },
  
  /**
   * Processes CIM Probe query results and updates ci/gr data where needed.
   * @override
   * @param { string queryName:{string propertyName:string value}[] cimObjects} results
   * @param Ci deviceCi
   * @throws Error
   */
  processCim: function(results, deviceCi) {
  	throw new DiscoveryException('CimSensor.processCim() needs to be overridden in sensor `' + this.getSensorName() + '`.');
  },
  
  /**
   * Calls processCim().
   * @override
   * @param XMLObj(//result) probeResult
   * @throws DiscoveryException On error.
   */
  process: function(probeResult) {
  	try {
  		this._queryResults = this._prepareQueryResults(probeResult);
  		this._deviceId = ''+this.getCmdbCi();
  		this._deviceToken = ''+this.getParameter(CimSensor.PARAMETER_DEVICE_INSTANCE);
  		this._instanceId = ''+this.getParameter(CimSensor.PARAMETER_INSTANCE_ID);
  		this._instanceToken = ''+this.getParameter(CimSensor.PARAMETER_INSTANCE);
  		
  		if (typeof this.versionEra === 'undefined' || this.versionEra === 'fuji')
  			this.processCim(this._queryResults, this.getDeviceCi());
  		else
  			this.processCim(this._queryResults, this._instanceId, this._deviceId, this._instanceToken, this._deviceToken, current);				
  	} catch (e) {
  		throw new DiscoveryException('CIM Sensor error: ' + e, e);
  	}
  },
 
  sha1: function(data) {
  	return ''+Packages.org.apache.commons.codec.digest.DigestUtils.shaHex(data);
  },
  
  defineRelation: function(name, fromId, nature, toTable, keys, options) {
  	if (this._related[name] === undefined)	
  		this._related[name] = new CimSensor.Relation(fromId, nature, toTable, keys, options);

  	return this._related[name];
  },
  
  defineInstanceRelation: function(name, nature, toTable, keys, options) {
  	return this.defineRelation(name, this._instanceId, nature, toTable, keys, options);
  },
  
  defineDeviceRelation: function(name, nature, toTable, keys, options) {
  	return this.defineRelation(name, this._deviceId, nature, toTable, keys, options);
  },
  
  addRelated: function(relationName, cidata) {
  	if (this._related[relationName] === undefined)
  		throw new IllegalArgumentException('CIData relation `' + relationName + '` undefined.');
  			
  	this._related[relationName].cidata.push(cidata);
  	return cidata;
  },
  
  defineTriggeredProbe: function(key, probeName) {
  	var probeCache = new SNC.ProbeCache();
  	var probeRecord = probeCache.getByName(probeName);
  	
  	if (!probeRecord)
  		throw new IllegalArgumentException('Unknown triggered probe `' + probeName + '`.');
  	
  	this._triggeredProbes[key] = {
  		id: ''+probeRecord.sys_id
  	};

  	return this._triggeredProbes[key];
  },
  
  triggerBatch: function(probeKey, instance, cidata) {
  	if (typeof this._triggeredBatches[probeKey] === 'undefined')
  		this._triggeredBatches[probeKey] = [];
  	
  	this._triggeredBatches[probeKey].push({
  		instance: instance,
  		cidata: cidata
  	});
  },
  
  /**
   * Retrievs all CIM query result sets, mapped by query name.
   * @return { name: XMLObj(//cimqueryset/result/instance) }
   */
  getQueryResults: function() {
  	return this._queryResults;
  },
  	
  /**
   * Retrieves a CIM query result set of CIM instances by query name.
   * @param string name
   * @return null|XMLObj(//cimqueryset/result/instance)
   */
  getQueryResult: function(name) {
  	return this._queryResults[name];
  },
  
  getProbeId: function() {
  	return this.getParameter(CimSensor.PARAMETER_PROBE);
  },
  
  /**
   * @param XMLObj(//result) probeResult 
   * @return { name: XMLObj(//cimqueryset/result/instance) }
   */
  _prepareQueryResults: function(probeResult) {
  	var namedQueries = new CimProbe(this.getProbeId()).getNamedStatements();
  	var cimProbeResult = new CimProbeResult(probeResult);
  	return cimProbeResult.getNamedInstances(namedQueries);
  },
  
  after: function() {
  	this._reconcileRelations();
  	this.afterReconcile();
  	this._fireTriggeredProbes();
  },
  
  /**
   * Called during CimSensor.after(), after relationships are saved, before probes are triggered.
   * @abstract
   */
  afterReconcile: function() {
  },
  
  /**
   * Reconciles relationships created by addBatch().
   */
  _reconcileRelations: function() {
  	// lock against this CI and probe, prevent sync issues between multiple results for the same probe
  	var mutexMetricName = 'CimSensor';
  	var key = this.sha1(this._deviceId + '::' + this.getProbeId());
  	var mutex = new SelfCleaningMutex(key, mutexMetricName);
  	mutex.setSpinWait(200); // 200 ms
  	mutex.setMaxSpins(1500); // 5 mins
  	if (mutex.get()) {
  		try {
  			for (var name in this._related) {
  				var relation = this._related[name];
  				
  				var reconcilerOptions = {
  					fromId: relation.fromId,
  					nature: relation.nature,
  					toTable: relation.toTable,
  					cidata: relation.cidata,
  					keys: relation.keys,
  					retireMissing: relation.options.retireMissing,
  					type: this.type,
  					direction: relation.options.direction
  				};
  				
  				var reconciler = new DiscoveryCIRelationReconciler(reconcilerOptions);
  				reconciler.reconcile();
  			}
  		} finally {
  			mutex.release();
  		}
  	} else {
  		gs.logError('Unable to lock on CimSensor mutex for: ' + this._deviceId + '::' + this.getProbeId());
  	}
  },
  
  /**
   * Retrieves all triggered probe IDs for this sensor. Does not use ASensor.
   * TODO: Alter _fireTriggeredProbes to use this method.
   * @returns [string]
   */
  _getTriggeredProbeIDs: function() {
  	var gr = new GlideRecord('discovery_sensor_probe');
  	gr.addQuery('sensor', sensorId); // global sensorId set by ASensor
  	gr.query();
  	
  	var probeIds = [];
  	while (gr.next())
  		probeIds.push(''+gr._probe);
  	
  	return probeIds;
  },
  
  _fireTriggeredProbes: function() {
  	for (var key in this._triggeredProbes) {
  		var probeMeta = this._triggeredProbes[key];
  		var batches = this._triggeredBatches[key];
  		if (gs.nil(batches))
  			continue;
  		
  		for (var bi = 0, bn = batches.length; bi < bn; ++bi) {
  			var batch = batches[bi];
  			var instanceId = gs.nil(batch.cidata.data.sys_id) ? null : batch.cidata.data.sys_id;
  			var probe = Probe.getById(probeMeta.id);
  			if (!probe)
  				throw new IllegalArgumentException('Probe `' + probeMeta.id + '` not found.');

  			var parameters = {
  				source: this.getSource(),
  				port: ''+this.getParameter('port'),
  				sys_id: this._deviceId,
  				ecc_breadcrumbs: ''+this.getEccQueueId(),
  				device_instance: this._deviceToken,
  				namespace: batch.instance._namespace,
  				instance:  CimInstanceToken.parse(batch.instance),
  				instance_id: instanceId
  			};

  			for (var name in parameters)
  				probe.addParameter(name, parameters[name]);
  			
  			probe.setSource(parameters.source);
  			new ProbeHandlerCim(probe).run();
  			probe.create(this.getAgent(), this.getEccQueueId());			
  		}
  	}
  },
  
  type: 'CimSensor'
});

Sys ID

c6fdb2133705200032ff8c00dfbe5d2d

Offical Documentation

Official Docs: