Name

global.CIRelationWalker

Description

Traverses a relationship schema for CIs.

Script

// Discovery
/**
* Traverses a relationship schema for CIs.
* @author roy.laurie
*
* Example usage:
*
* var ciwalker = new CIRelationWalker('cmdb_ci_storage_server(Runs on::Depends on,Installed on::Hosted on).cmdb_ci_storage_pool.cmdb_ci_storage_volume');
* var server = null;
* // perform a reverse-traversal back up to the server record, from the volume record.
* ciwalker.moonwalk(volumeci, function(serverRecord) {
*    gs.log('Volume CI lives on server: ' + serverRecord.name);
*    server = serverRecord;
* });
* // perform a traversal from the server record down to all of its volume records
* ciwalker.walk(server, function(volumeRecord) {
*    gs.log('Server CI has volume: ' + volumeRecord.lun);
* });
*
* Or build using cascaded calls:
*
* var ciwalker = new CIRelationWalker()
* .addRelation('cmdb_ci_storage_server', [ 'Runs On::Depends On', 'Installed on::Hosted on' ], 'cmdb_ci_storage_pool')
* .addRelation('cmdb_ci_storage_pool', , 'Runs On::Depends On', 'cmdb_ci_storage_volume');
* .addRelation('cmdb_ci_storage_pool', , [], 'cmdb_ci_storage_volume');
*/
var CIRelationWalker = Class.create();

/**
* Represents a single relationship node in the walker schema.
* @param string originTable The parent table of the relationship.
* @param string targetTable The child table of the relationship.
* @param string|[string] The nature(s) of the relationship.
*/
CIRelationWalker.Relation = function(originTable, natures, targetTable) {
  this.originTable = originTable;
  this.targetTable = targetTable;
  this.relationTypeIds = [];
  
  if (gs.nil(natures))
  	return;
 
  if (typeof(natures) === 'string') {
  	this.relationTypeIds.push(new DiscoveryFunctions().findCIRelationshipType('cmdb_rel_type', natures));
  } else {
  	for (var i = 0, n = natures.length; i < n; ++i)
  		this.relationTypeIds.push(new DiscoveryFunctions().findCIRelationshipType('cmdb_rel_type', natures[i]));
  }
};

CIRelationWalker.prototype = {
  /**
   * @param string|undefined relationQuery Optionally pass a dot-notation query to build the schema.
   */
  initialize: function(relationQuery) {
  	this._debugLogger = null;

  	if (!gs.nil(relationQuery))
  		this._relations = this._parseQuery(relationQuery);
  	else
  		this._relations = [];
  },
 
  /**
   * @param Function((string), (string)class, (string)method)|null The debug logger to use or null (default) to disable.
   */
  setDebugLogger: function(debugLogger) {
  	this._debugLogger = debugLogger;
  },
 
  /**
   * @param string query
   * @return [CIRelationWalker.Relation]
   */
  _parseQuery: function(query) {
  	var relations = [];
  	var originTable = null;
  	var originNatures = null;
  	var tokens = query.split(/\./);
  	for (var i = 0, n = tokens.length; i < n; ++i) {
  		var parts = tokens[i].split(/\(/); // bug in rhino prevents proper regex here
  		if (parts[1] === undefined) {
  			var table = tokens[i];
  			var natures = [];
  		} else {
  			var table = parts[0];
  			var natures = parts[1].substr(0, parts[1].length - 1);
  			natures = natures.split(/,/);
  		}

  		if (originTable !== null)
  			relations.push(new CIRelationWalker.Relation(originTable, originNatures, table));
  	   
  		originTable = table;
  		originNatures = natures;
  	}
     
  	return relations;
  },
 
  /**
   * @param string originTable
   * @param string|[string] natures The CI relationship type description(s). Single or array. E.g., 'Runs on::Hosted on'
   * @return CIRelationWalker this. Allows for cascaded calls.
   */
  addRelation: function(originTable, natures, targetTable) {
      this._relations.push(new CIRelationWalker.Relation(originTable, natures, targetTable));
      return this;
  },
 
  /**
   * Traverses the relationship schema from origin (parent) to target (child).
   * @param string originId The origin (parent) CI id to start from.
   * @param Function(targetId) The listener function to call iteratively, for each target CI found.
   */
  walk: function(originId, onTarget) {
      this._traverse([originId], 0, true, onTarget);
  },
 
  /**
   * Traverses the relationship schema from target (child) to origin (parent), effectively in reverse.
   * @param string targetId The target (child) CI id to start from.
   * @param Function(originId) onOrigin The listener function to call iteratively, for each origin CI found.
   */
  moonwalk: function(targetId, onOrigin) {;
      this._traverse([targetId], this._relations.length - 1, false, onOrigin);
  },
 
  /**
   * Recursive.
   * @param [string] ids The IDs to use as origin (walk) or target (moonwalk).
   * @param integer relationsIndex The index of the CIRelationWalker.Relation in the schema to query against.
   * @param boolean isIncrement TRUE if walking, FALSE if moonwalking.
   * @param Function(id) onEnd The listener function call iteratively, if an end-point CI is found.
   */
  _traverse: function(ids, relationsIndex, isIncrement, onEnd) {
      var relation = this._relations[relationsIndex];
  	var relationRecords = new GlideRecord('cmdb_rel_ci');
     if (isIncrement) { // for walk()
  	    relationRecords.addQuery('parent', ids);
  		relationRecords.addQuery('child.sys_class_name', relation.targetTable);
      } else { // for moonwalk()
  	    relationRecords.addQuery('child', ids);
  		relationRecords.addQuery('parent.sys_class_name', relation.originTable);	    
  	}
     
  	if (relation.relationTypeIds !== null && relation.relationTypeIds.length > 0)
  	    relationRecords.addQuery('type', relation.relationTypeIds);
  	
  	relationRecords.query();
     
  	if (this._debugLogger)
  		var relationIds = [];
     
  	var nextIds = [];
  	while (relationRecords.next()) {
  		if (this._debugLogger)
  			relationIds.push(''+relationRecords.sys_id);
  		   
  		nextIds.push(this._getNextId(relationRecords, isIncrement));
  	}
     
  	var isEnd = ( ( isIncrement && (relationsIndex + 1) >= this._relations.length ) || ( !isIncrement && (relationsIndex - 1) < 0 ) );
  	var index = ( isIncrement ? relationsIndex + 1 : relationsIndex - 1 );
  	   
  	if (this._debugLogger)
  		this._logTraversal(ids, relation, relationIds, nextIds, isEnd);
     
  	// call the listener for end-point CIs if it's time
  	if (isEnd) {
  		for (var i = 0 , n = nextIds.length; i < n; ++i)
  	        onEnd(nextIds[i]);
     
          return;
      }

  	this._traverse(nextIds, index, isIncrement, onEnd);
  },

  _logTraversal: function(ids, relation, relationshipIdsFound, cisFound, isEnd) {
  	var payload = {
  		forCIs: ids,
  		relation: relation,
  		foundRelationships: relationshipIdsFound,
  		foundCIs: cisFound,
  		isEnd: isEnd
  	};

  	this._debugLogger(JSUtil.describeObject(payload, 'debug'), this.type, '_traverse');
  },
 
  /**
   * Retrieves the child or parent sys id for a relationship record depending on walk() or moonwalk().
   */
  _getNextId: function(relationshipRecord, isIncrement) {
  	return '' + ( isIncrement ? relationshipRecord.child : relationshipRecord.parent );
  },
 
  type: 'CIRelationWalker'
}

Sys ID

306de1c637132000dcd48c00dfbe5dca

Offical Documentation

Official Docs: