Name

global.StorageReconciler

Description

Reconciles between storage exports and computer mounts that consume them, creation relationships where necessary.

Script

var StorageReconciler = Class.create();

StorageReconciler.prototype = {
  initialize: function() {},
  	
  /**
   * Searches the CMDB for the Storage Volume (LU - Logical Unit) that is exporting the specified
   * network disk via Fibre Channel (SAN). The SAN target exports a volume to the host client. The host client
   * (Windows, Linux) imports the volume as a SAN Disk (cmdb_ci_san_disk). Creates a relationship if match found.
   *
   * 
   * @param cmdb_ci_fc_disk sys_id The disk on the Host machine that is importing the volume.
   * @param array targetWWPNs The target world-wide-port-names to search for.
   */	
  createFCDiskToVolumeRel: function(diskSysId, lun, targetWWPNs, initiatorWWPN) {
  	// Find the fc export so we can find the volume
  	var exportsGr = this.findFCExports(lun, targetWWPNs, initiatorWWPN);
  					
  	// Finally create the relationship
  	return this._createDiskToVolumeRel(diskSysId, exportsGr);
  }, 
  
  /**
   * Searches the CMDB for the Storage Volume (LU - Logical Unit) that is exporting the specified
   * network disk via ISCSI (SAN). The ISCSI target exports a volume to the host client. The host client
   * (Windows, Linux) imports the volume as a SAN Disk (cmdb_ci_iscsi_disk). Creates a relationship if match found.
   *
   * 
   * @param cmdb_ci_iscsi_disk sys_id The disk on the Host machine that is importing the volume.
   */		
  createISCSIDiskToVolumeRel: function(diskSysId, lun, targetIQN, initiatorIQN) {
  	// Find the iscsi export so we can find the volume
  	var exportsGr = this.findISCSIExports(targetIQN, lun, initiatorIQN);

  	// Finally create the relationship			
  	return this._createDiskToVolumeRel(diskSysId, exportsGr);
  },
  
  /**
   * Find FC Exports based an array of targetWWPNs and lun
   */
  findFCExports: function(lun, targetWWPNs, initiatorWWPNs) {

  	// See if we can find exports based on the initiator
  	gr = new GlideRecord('cmdb_ci_fc_export');
  	gr.addQuery('initiator_wwpn', initiatorWWPNs);
  	if (JSUtil.notNil(lun))
  		gr.addQuery('lun', "" + lun);
  	gr.query();
  	if (gr.hasNext())
  		return gr;

  	// No exports by initiator, do it the old slow & complex way.
  	// Find the controller
  	var controllerSysIds = this._findControllers(targetWWPNs);
  	if (controllerSysIds.length == 0) 
  		throw 'Controller is empty for FC Port' + targetWWPNs.toString(); 
  	
  	// Find the fc exports
  	var gr = new GlideRecord('cmdb_ci_fc_export');
  	gr.addQuery('exported_by', controllerSysIds);
  	
  	// Just in case we don't have lun, maybe we get lucky and only one export otherwise we will create relationships with all, better than none.
  	if (JSUtil.notNil(lun)) {
  		var qc = gr.addNullQuery('lun');
  		qc.addOrCondition('lun', '' + lun);
  	}

  	if (initiatorWWPNs instanceof Array) {
  		if (initiatorWWPNs.length == 1)
  			gr.addQuery("initiator_wwpn", initiatorWWPNs[0]);
  		else
  			gr.addQuery("initiator_wwpn", 'IN', initiatorWWPNs);
  	}
  	else
  		gr.addQuery("initiator_wwpn", initiatorWWPNs);

  	gr.query();
  	if (!gr.hasNext())
  		throw "FC Export not found for targetWWPNs: " + targetWWPNs + " lun: " + lun + " controllerSysIds: " + controllerSysIds.toString() + " initiatorWWPN: " + JSON.stringify(initiatorWWPNs);
  		
  	return gr;
  }, 
  
  /**
   * Find ISCSI Exports based on IQN and lun
   */
  findISCSIExports: function(targetIQN, lun, initiatorIQN) {

  	// It should be OK to reconcile on either the initiator
  	// or target IQN.  We've reconciled on both since Geneva but
  	// we've recently encountered cases where we can't find the
  	// target IQN on the storage server.

  	var gr = getQuery(true);
  	if (!gr.hasNext()) {
  		// We couldn't find an export with the same target and initiator.  Try
  		// again with just the initiator.
  		gr = getQuery();
  		if (!gr.hasNext())
  			throw "iSCSI Export not found for target IQN: " + targetIQN + " lun: " + lun;
  	}

  	return gr;

  	function getQuery(includeTarget) {
  		var gr = new GlideRecord('cmdb_ci_iscsi_export');

  		includeTarget && gr.addQuery('iqn', targetIQN);
  		gr.addQuery('initiator_iqn', initiatorIQN);

  		// Just in case we don't have lun, maybe we get lucky and only one export
  		if (lun || (lun === 0)) {
  			var qc = gr.addNullQuery('lun');
  			gr.addOrCondition('lun', lun);
  		}

  		gr.query();

  		return gr;
  	}
  },

  queryFcPorts: function(wwpns) {
  	// query for Fibre Channel Ports that match wwpns
  	var gr = new GlideRecord('cmdb_ci_fc_port');
  	gr.addQuery('wwpn', wwpns);
  	gr.query();
  	return gr;
  },
  
  /**
   * Get volume sys id from storage exports
   */	
  getVolumesFromExports: function(exportsGr) {
  	var seenVolumes = {};

  	while (exportsGr.next()) {
  		// Make sure we have not seen same volume just in case
  		var volumeSysId = ''+exportsGr.storage.sys_id;
  		if (!seenVolumes[volumeSysId]) 
  			seenVolumes[volumeSysId] = true;	
  	}
  	
  	return seenVolumes;
  },

  /**
   * Find controller sysIds from an array of targetWWPNs.
   */ 
  _findControllers: function(targetWWPNs) {
  	var portsGr = this.queryFcPorts(targetWWPNs);
  	var controllerSysIds = []; 
  	var portSysIds = [];

  	// Get all port sysIds
  	while (portsGr.next()) 
  			portSysIds.push('' + portsGr.sys_id);
  	
  	
  	// Get controllers from cmdb_rel_ci
  	var df = new DiscoveryFunctions();
  	var typeId = df.findCIRelationshipType("cmdb_rel_type", "Controller for::Controlled by");
  	var rels = new GlideRecord("cmdb_rel_ci");
  	rels.addQuery("child", portSysIds);
  	rels.addQuery("type", typeId + '');		
  	rels.query();
  	
  	while (rels.next()) {
  		controllerSysIds.push('' + rels.parent);
  	}
  	
  	return controllerSysIds;
  },

  /**
   * Create a cmdb_rel_ci relationship between disk sys_id and volume with all values in exportsGr.
   */
  _createDiskToVolumeRel: function(diskSysId, exportsGr) {
  	// Get volumes and create relationship
  	var seenVolumes = this.getVolumesFromExports(exportsGr);
  	var df = new DiscoveryFunctions();
  	for (var volumeSysId in seenVolumes) {
  		df.createRelationshipIfNotExists(volumeSysId, diskSysId, 'Exports to::Imports from');
  	}

  	// See if this disk is related to a vCenter datastore_disk.  If so, create a relationship from
  	// it to the volume also.
  	var relCi = new GlideRecord('cmdb_rel_ci');
  	relCi.addQuery('child', diskSysId);
  	relCi.addQuery('parent.sys_class_name', 'cmdb_ci_vcenter_datastore_disk');
  	relCi.addQuery('type', '0e8ffb1537303100dcd445cbbebe5d40');   // sys_id of "Exports to::Imports from"
  	relCi.query();
  	if (relCi.next()) {
  		for (volumeSysId in seenVolumes)
  			df.createRelationshipIfNotExists(volumeSysId, relCi.parent, 'Exports to::Imports from');
  	}

  	return seenVolumes;
  },	

  type: 'StorageReconciler'
};

Sys ID

e2f2110347300200d06d6f2ccee490e8

Offical Documentation

Official Docs: