Name

global.DiscoverySmiStorageServerSensor

Description

Processes SAN & NAS Storage Arrays / Servers per SMI-S.

Script

// Discovery
ArrayPolyfill;
/**
* Processes SAN & NAS Storage Arrays / Servers per SMI-S.
* @author roy.laurie
* @since fuji
*/
var DiscoverySmiStorageServerSensor = Class.create();

DiscoverySmiStorageServerSensor._STORAGE_DEVICE_INTERFACE_TRANSLATIONS = {
  'sas': 'sas',
  'scsi': 'scsi',
  'sata': 'sata',
  'default': null
};

DiscoverySmiStorageServerSensor.prototype = Object.extendsObject(CimSensor, {
  processCim: function(results, deviceCi) {
  	var diskCis = this._processDisks(results, deviceCi);
  	var poolCis = this._processPools(results, deviceCi);
  	var volumeCis = this._processVolumes(results, deviceCi, poolCis);
  	var controllers = this._processControllers(results, deviceCi);
  	var fcPortCis = this._processFcPorts(results, deviceCi, controllers.pathToCi);

  	this._processFcExports(results, deviceCi, volumeCis, controllers);
  },
  
  _processDisks: function(results, deviceCi) {
  	var ciSchema = this.getCiSchema();
  	var diskCis = {};

  	for (var i = 0; i < results.diskDrives.length; ++i) {
  		var diskData = results.diskDrives[i];

  		if (this.isDebugging())
  			Debug.logObject('Disk Drive result', diskData);

  		// find the associated media
  		var diskMediaData = null;
  		for (var j = 0; j < results.diskMedia.length; ++j)  {
  			if (results.diskMedia[j]._associations[0].originPath === diskData._path) {
  				diskMediaData = results.diskMedia[j];
  				break;
  			}
  		}

  		// compute size
  		var dataSize;
  		if (diskMediaData) {
  			dataSize = new StorageDataSize((diskMediaData.NumberOfBlocks * diskMediaData.BlockSize) || 0);
  		
  			if (this.isDebugging())
  				Debug.logObject('Disk Media result', diskMediaData);
  		}
  		else {
  			dataSize = new StorageDataSize(0);				
  			this.warn('Unable to find disk media for drive: ' + diskData.DeviceID);
  		}
  		
  		var diskCi = ciSchema.createCi('cmdb_ci_disk', {
  			name: diskData.ElementName || diskData.DeviceID,
  			device_id: diskData.DeviceID,
  			computer: deviceCi,
  			storage_type: 'disk',
  			drive_type: 'disk' // deprecated
  		});
  		dataSize.applyToStorageDeviceSize(diskCi);

  		this._setCiLocation(diskCi);
  		deviceCi.addReferral(diskCi);
  		diskCis[diskData._path] = diskCi;

  		if (diskData._classname.indexOf('Clar_') === 0) // EMC
  			this._processEmcDisk(results, deviceCi, diskData, diskCi);

  		if (this.isDebugging())
  			Debug.logObject('Disk CI', diskCi.toShallowObj());
  	}

  	return diskCis;
  },

  /**
   * Handle EMC-specific storage devices.
   */
  _processEmcDisk: function(results, deviceCi, diskData, diskCi) {
  	diskCi.data.serial_number = diskData.EMCSerialNumber;
  	diskCi.data.device_interface = this._translate(diskData.Caption, DiscoverySmiStorageServerSensor._STORAGE_DEVICE_INTERFACE_TRANSLATIONS);

  	// make / model
  	var makeModel = MakeAndModelJS.fromNames(diskData.EMCManufacturer, null);
  	diskCi.data.manufacturer = ''+makeModel.getManufacturerSysID();
  	diskCi.data.model_id = ''+makeModel.getModelNameSysID();

  	diskCi.data['interface'] = diskCi.data.device_interface; // deprecated
  },

  _processPools: function(results, deviceCi) {
  	var ciSchema = this.getCiSchema();
  	var poolCis = {};

  	for (var i = 0; i < results.pools.length; ++i) {
  		var poolData = results.pools[i];

  		if (this.isDebugging())
  			Debug.logObject('Pool result', poolData);

  		var dataSize = new StorageDataSize(poolData.TotalManagedSpace || 0);
  		var freespaceSize = new StorageDataSize(poolData.RemainingManagedSpace || 0);

  		var poolCi = ciSchema.createCi('cmdb_ci_storage_pool', {
  			name: poolData.PoolID,
  			pool_id: poolData.PoolID,
  			short_description: poolData.ElementName,
  			hosted_by: deviceCi
  		});
  		dataSize.applyToStoragePoolSize(poolCi);
  		freespaceSize.applyToStoragePoolFreeSpace(poolCi);

  		this._setCiLocation(poolCi);

  		deviceCi.addReferral(poolCi);

  		poolCis[poolData._path] = poolCi;

  		if (this.isDebugging())
  			Debug.logObject('Pool CI', poolCi.toShallowObj());
  	}

  	return poolCis;
  },

  _processVolumes: function(results, deviceCi, poolCis) {
  	var ciSchema = this.getCiSchema();
  	var volumeCis = {};

  	for (var i = 0; i < results.volumes.length; ++i) {
  		var volumeData = results.volumes[i];

  		if (this.isDebugging())
  			Debug.logObject('Volume result', volumeData);

  		var providedByCi = null;
  		// if this is derived from a pool, find the pool CI
  		for (var v = 0; v < results.poolVolumes.length; ++v) {
  			var poolVolumeData = results.poolVolumes[v];
  			if (poolVolumeData._path !== volumeData._path)
  				continue;

  			var poolPath = poolVolumeData._associations[0].originPath;
  			if (typeof poolCis[poolPath] !== 'undefined')
  				providedByCi = poolCis[poolPath];
  			else
  				this.warn('Unable to find Pool for Volume: ' + volumeData.ElementName);

  			break;
  		}

  		var dataSize = new StorageDataSize((volumeData.NumberOfBlocks * volumeData.BlockSize) || 0);
  		var freespaceSize = new StorageDataSize((volumeData.ConsumableBlocks * volumeData.BlockSize) || 0);
  		var lun = parseInt(volumeData.DeviceID, 16);
  		lun = ( lun != lun ? null : ''+lun ); // if it was a non-integer, set to null. otherwise cast to string

  		var volumeCi = ciSchema.createCi('cmdb_ci_storage_volume', {
  			computer: deviceCi,
  			name: volumeData.ElementName,
  			media_type: 'fixed',
  			lun: lun,
  			volume_id: volumeData.DeviceID,
  			provided_by: providedByCi
  		});
  		dataSize.applyToStorageVolumeSize(volumeCi);
  		freespaceSize.applyToStorageVolumeFreeSpace(volumeCi);

  		this._setCiLocation(volumeCi);

  		deviceCi.addReferral(volumeCi);

  		if (providedByCi !== null)
  			providedByCi.addReferral(volumeCi);

  		volumeCis[volumeData.DeviceID] = volumeCi;

  		if (this.isDebugging())
  			Debug.logObject('Volume CI', volumeCi.toShallowObj());
  	}

  	return volumeCis;
  },

  _processControllers: function(results, deviceCi) {
  	var controllerPathToInitiator = {};
  	
  	mapInitiators();
  	
  	// Handle specific controllers 
  	// Get the first one see what type of controller/storage server we are dealing with
  	if (results.scsiControllers.length > 0) {
  		var controllerClass = results.scsiControllers[0]._classname;
  		
          if (controllerClass.indexOf('ONTAP_') === 0)
  			return this._processNetAppControllers(results, deviceCi, controllerPathToInitiator);
  	}

  	return this._processAllOtherControllers(results, deviceCi, controllerPathToInitiator);
  	
  	function mapInitiators() {
  		var hardwareMap = { };      // Hardware ID mapped by origin path

  		results.hardwareID.forEach(function(hardware) { hardwareMap[hardware._associations[0].originPath] = hardware.StorageID; });
  		results.authorizedTarget.forEach(function(target) { controllerPathToInitiator[target._associations[0].originPath] = hardwareMap[target._path]; });
  	}
  },

  // NetApp Controller have the following characteristics:
  // 1. For iSCSI, there are 3 classes - ONTAP_iSCSINode, ONTAP_ProtocolController, ONTAP_iSCSIProtocolController
  //		a. ONTAP_iSCSINode has an iqn but is essentially not needed
  //		b. ONTAP_ProtocolController is the controller and there is a relationiship from the volume to it.
  //		c. ONTAP_iSCSIProtocolController has the relationship from iqn to the appropriate protocol controller.
  // 2. For FC, there are 2 classes - ONTAP_ProtocolController, ONTAP_SCSIFCProtocolController
  //		a. ONTAP_ProtocolController is the controller and there is a relationiship from the volume and FCPort to it
  //		b. ONTAP_SCSIFCProtocolController - Has WWPN, but no way to associate it to the Prototocl Controller. 
  //		   However Not really needed since we have all the information from ONTAP_ProtocolController.
  // The algorithm is to find all ONTAP_ProtocolContoller, create a CI for it. Also, we find the IQN for ISCSI devices
  _processNetAppControllers: function(results, deviceCi, controllerPathToInitiator) {
  	var ciSchema = this.getCiSchema();
  	var controllerPathToCi = {};
  	var controllerIdToIQN = {};
  	var idToInitiator = {};
  	var controllerIdToCi = {};
  	var controllerComputedIdToCi = {};
  	var controllers = {};
  	
  	for (var i = 0; i < results.scsiControllers.length; ++i) {
  		var controllerData = results.scsiControllers[i];

  		if (this.isDebugging())
  			Debug.logObject('SCSI Controller result', controllerData);

  		// Only care about protocol controllers here
  		if (controllerData.NameFormat === '3' || controllerData.NameFormat === '2')
  			continue;

  		// Lets find the iqn if any and create mapping
  		var iqn = this._findNetAppControllerIQN(results, deviceCi, controllerData);

  		var deviceId, name;
  		if (iqn) {
  			controllerIdToIQN[controllerData.DeviceID] = iqn;
  			deviceId = iqn;
  			name = iqn;
  		}
  		else {
  			deviceId = controllerData.DeviceID;
  			name = controllerData.ElementName || controllerData.DeviceID; 
  		}

  		idToInitiator[controllerData.DeviceID] = controllerPathToInitiator[controllerData._path];
  		
  		// Create controller if we have not seen before
  		var controllerCi = controllerComputedIdToCi[deviceId];
  		if (!controllerCi) {
  			controllerCi = ciSchema.createCi('cmdb_ci_storage_controller', {
  				name: name,
  				device_id: deviceId,
  				computer: deviceCi
  			});
  			
  			this._setCiLocation(controllerCi);
  			deviceCi.addReferral(controllerCi);
  			controllerComputedIdToCi[deviceId] = controllerCi;
  		
  			if (this.isDebugging())
  				Debug.logObject('NetApp Controller CI', controllerCi.toShallowObj());	
  		}		
  	
  		controllerIdToCi[controllerData.DeviceID] = controllerCi;
  		controllerPathToCi[controllerData._path] = controllerCi;
  	}
  	
  	// Construct the object to return
  	controllers.pathToCi = controllerPathToCi;
  	controllers.idToIQN = controllerIdToIQN;
  	controllers.idToCi = controllerIdToCi;
  	controllers.idToInitiator = idToInitiator;

  	return controllers;
  },

  // Helper function to find the IQN, if iSCSI, by relating a ONTAP_ProtocolController to ONTAP_iSCSIProtocolController
  _findNetAppControllerIQN: function(results, deviceCi, protocolControllerData) {
  	var iqn;

  	for (var i = 0; i < results.scsiControllers.length; ++i) {
  		var controllerData = results.scsiControllers[i];

  		// Only care about the relationship between ISCSI to ProtocolContoller (e.g. ONTAP_iSCSIProtocolController)
  		if (controllerData.NameFormat !== '3')
  			continue;
  		
  		// The DeviceID contains the iqn and the id of the protocol controller split by |
  		var arr = controllerData.DeviceID.split("|");
  		if (arr.length != 2)
  			continue;

  		var tiqn = arr[0];			
  		var protocolControllerDeviceId = arr[1];
  		
  		// Lets see if we have a match
  		if (protocolControllerData.DeviceID.indexOf(protocolControllerDeviceId) >= 0) {
  			iqn = tiqn;
  			break;
  		}
  	}
  	
  	return iqn;
  },
  
  // Default method to process all other controllers
  _processAllOtherControllers: function(results, deviceCi, controllerPathToInitiator) {
  	var ciSchema = this.getCiSchema();
  	var controllerPathToCi = {};
  	var controllerIdToIQN = {};
  	var controllerIdToCi = {};
  	var controllers = {};
  	var idToInitiator = {};
  	
  	for (var i = 0; i < results.scsiControllers.length; ++i) {
  		var controllerData = results.scsiControllers[i];

  		if (this.isDebugging())
  			Debug.logObject('SCSI Controller result', controllerData);

  		if (controllerData.NameFormat === '3') {
  			controllerIdToIQN[controllerData.DeviceID] = controllerData.Name;
  		}
  		
  		idToInitiator[controllerData.DeviceID] = controllerPathToInitiator[controllerData._path];
  		
  		var controllerCi = controllerIdToCi[controllerData.DeviceID];
  		if (!controllerCi) {
  			controllerCi = ciSchema.createCi('cmdb_ci_storage_controller', {
  				name: ( controllerData.ElementName || controllerData.DeviceID ),
  				device_id: controllerData.DeviceID,
  				computer: deviceCi
  			});

  			this._setCiLocation(controllerCi);
  			deviceCi.addReferral(controllerCi);
  			controllerIdToCi[controllerData.DeviceID] = controllerCi;

  			if (this.isDebugging())
  				Debug.logObject('Controller CI', controllerCi.toShallowObj());			

  		}

  		controllerPathToCi[controllerData._path] = controllerCi;			
  	}
  	
  	// Construct the object to return
  	controllers.pathToCi = controllerPathToCi;
  	controllers.idToIQN = controllerIdToIQN;
  	controllers.idToCi = controllerIdToCi;
  	controllers.idToInitiator = idToInitiator;
  	
  	return controllers;
  },
  
  _processFcPorts: function(results, deviceCi, controllerPathToCi) {
  	var ciSchema = this.getCiSchema();
  	var fcPortCis = {};

  	for (var i = 0; i < results.fcPorts.length; ++i) {
  		var fcPortData = results.fcPorts[i];

  		if (this.isDebugging())
  			Debug.logObject('FC Port result', fcPortData);

  		var controllerCis = [];
  		// Don't expect m2m relationships here, but get all of them
  		for (var a = 0; a < fcPortData._associations.length; ++a) {
  			var controllerCimPath = fcPortData._associations[a].originPath;

  			// It is possible to not have a mapping because it is not pointing to an actual physical controller, so don't worry about it
  			if (!controllerPathToCi[controllerCimPath])
  				continue;

  			var controllerCi = controllerPathToCi[controllerCimPath];
  			controllerCis.push(controllerCi);    
  		}
  		
  		var fcPortCi = ciSchema.createCi('cmdb_ci_fc_port', {
  			name: fcPortData.ElementName,
  			wwpn: new DiscoveryWWN(fcPortData.PermanentAddress).toString(),
  			port_type: new DiscoveryFcPortType(fcPortData.PortType).getPortType(),
  			speed: new DiscoveryDataRate(fcPortData.Speed).to(DiscoveryDataRate.Units.GFC) + ' GFC',
  			short_description: fcPortData.ElementName,
  			computer: deviceCi,
  			//controller: controllerCis.length > 0 ? controllerCis[0] : null
  			controller: null
  		});
  		

  		this._setCiLocation(fcPortCi);
  		deviceCi.addReferral(fcPortCi);

  		// Create all relationships to be safe
  		for (var j=0; j < controllerCis.length; j++) 
  			controllerCis[j].addRelation(fcPortCi);
  		
  		if (controllerCis.length > 0)
  			controllerCis[0].addReferral(fcPortCi);
  		
  		fcPortCis[fcPortData._path] = fcPortCi;

  		if (this.isDebugging())
  			Debug.logObject('FC Port CI', fcPortCi.toShallowObj());
  	}

  	return fcPortCis;
  },

  _processFcExports: function(results, deviceCi, volumeCis, controllers) {
  	var ciSchema = this.getCiSchema();
  	var controllerIdToIQN = controllers.idToIQN;
  	var idToInitiator = controllers.idToInitiator;
  	var controllerIdToCi = controllers.idToCi;
  	var skippedControllers = controllers.skippedControllers;
  	var seenExportIds = {};
  	
  	for (var i = 0; i < results.controllerForLunAssociations.length; ++i) {
  		var assocData = results.controllerForLunAssociations[i];

  		if (this.isDebugging())
  			Debug.logObject('Controller For LUN result', assocData);

  		// find both the controller and volume CIs for the association
  		var controllerDeviceId = assocData._key.DeviceID[0];
  		var volumeId = assocData._key.DeviceID[1];
  
  		// We reconcile based on initator -- if this is not there (which is possible), just move on.
  		var initiator = idToInitiator[controllerDeviceId];
  		if (!initiator)
  			continue;
  		
  		// find volume
  		var volumeCi = volumeCis[volumeId];
  		if (!volumeCi) {
  			this.warn('Unable to find exported Volume: ' + volumeId);
  			continue;
  		}

  		// find controller
  		var controllerCi = controllerIdToCi[controllerDeviceId];
  		if (!controllerCi) {
  			// Dont warn if we know we skipped the controller
  			if (!skippedControllers || !skippedControllers[controllerDeviceId])
  				this.warn('Unable to find exporting Controller: ' + controllerDeviceId);
  			
  			continue;
  		}

  		// Have we seen this before, then no need to create another
  		var exportId = volumeCi.data.name + '--' + controllerCi.data.device_id;
  		if (seenExportIds[exportId])
  			continue;
  		seenExportIds[exportId] = true;
  		
  		// Create export ci
  		var iqn = controllerIdToIQN[controllerDeviceId];
  		var exportCi;
  		var mappedLun = ''+parseInt(assocData.DeviceNumber, 16);
  		if (iqn) 
  			exportCi = ciSchema.createCi('cmdb_ci_iscsi_export', {
  				export_id: exportId,
  				storage: volumeCi,
  				exported_by: controllerCi,
  				hosted_by: deviceCi,
  				iqn: iqn,
  				initiator_iqn: initiator,
  				lun: mappedLun,
  				name: volumeCi.data.name + ' IQN ' + iqn
  			});
  		else {
  			exportCi = ciSchema.createCi('cmdb_ci_fc_export', {
  				export_id: exportId,
  				storage: volumeCi,
  				exported_by: controllerCi,
  				hosted_by: deviceCi,
  				lun: mappedLun,
  				initiator_wwpn: StorageWWN.parse(initiator),
  				name: volumeCi.data.name + ' LUN ' + mappedLun
  			});
  		}
  		volumeCi.addReferral(exportCi);
  		controllerCi.addReferral(exportCi);
  		deviceCi.addReferral(exportCi);

  		if (this.isDebugging())
  			Debug.log('FC Export CI', exportCi.toShallowObj());
  	}
  },

  _translate: function(value, translationMap) {
  	var key = value.toLowerCase();
  	if (typeof translationMap[key] !== null)
  		return key;

  	return translationMap['default'];
  },

  type: 'DiscoverySmiStorageServerSensor'
});

Sys ID

d9027ab737322100dcd445cbbebe5d07

Offical Documentation

Official Docs: