Name

global.DiscoverySolarisStorageSensor

Description

Processes Solaris disk and file system information and updates the CMDB.

Script

var DiscoverySolarisStorageSensor = Class.create();

DiscoverySolarisStorageSensor._PROBE_DELIMITER_REGEX = /\[\[==PROBE:(\w+)==\]\]/;

DiscoverySolarisStorageSensor.prototype = Object.extendsObject(DiscoverySensor, {
  init: function() {
  	DiscoverySensor.prototype.init.apply(this, arguments);
  },

  process: function(result) {
  	var results = this._parseResult(result.output);

  	if (this.isDebugging())
  		Debug.logObject('Solaris Storage Results', results);

  	var deviceCi = this.getDeviceCi();
  	this._descriptiveDiskNames = true;

  	// pre-process linux <-> solaris device id mapping from the diff_iostat sub-probe for use by the disk processor
  	this._deviceIdMap = this._parseIostatDiff(results);

  	// process disks from the 'iostat' command
  	this._processDisks(results, deviceCi);
  	// process partitions from the 'prtvtoc' command
  	this._processPartitions(results, deviceCi);
  	// process zfs pools data from the 'zpool list' command
  	this._processZfsPools(results, deviceCi);
  	// process zfs pool member data from the 'zfs status' command
  	this._processZfsPoolMembers(results, deviceCi);
  	// process the df probe
  	this._processFileSystems(results, deviceCi);
  	// process the hba info
  	this._processHbaInfo(results, deviceCi);

  	// aggregate all attached disk space and update device capacity as an integer
  	var totalBytes = DiscoveryStorageUtilities.getAggregateDataSize(deviceCi);
  	deviceCi.data.disk_space = new StorageDataSize(totalBytes).to(StorageDataSize.Units.GB) | 0;
  },

  after: function() {
  	DiscoverySensor.prototype.after.apply(this, arguments);
  	this._reconcileNas();
  },

  /**
   * Processes disks based on the results returned from the 'iostat' command.
   * Associates the new disk Ci to the device Ci via reference.
   */
  _processDisks: function(results, deviceCi) {
  	if (typeof results.iostat === 'undefined') {
  		this.warn('No disk results were returned.');
  		return;
  	}

  	var ciSchema = this.getCiSchema();
  	var storageReconciler = new DiscoveryStorageAllocationReconciler({ ciSchema: ciSchema });
  	var iostatData = this._parseIostat(results.iostat);

  	for (var i = 0; i < iostatData.length; ++i) {
  		var data = iostatData[i];

  		// use the first word as the vendor
  		var makeModel = MakeAndModelJS.fromNames(data.Vendor, data.Product, 'hardware');
  		// size
  		var matches = data.Size.match(/<(\d+) bytes>/);
  		var size = new StorageDataSize( matches ? matches[1] : 0 );
  		// defaults
  		var table = 'cmdb_ci_disk';
  		var storageType = 'disk';
  		var deviceInterface = 'scsi';

  		// determine whether it's an iscsi disk and reset defaults if it is
  		var iscsiDevice = this._findIscsiDevice(results, data.deviceId);
  		if (iscsiDevice !== null) {
  			table = 'cmdb_ci_iscsi_disk';
  			storageType = 'network';
  			deviceInterface = 'iscsi';
  		}

  		// create the disk Ci
  		var diskCi = ciSchema.createCi(table, {
  			name: data.deviceId,
  			device_id: data.deviceId,
  			storage_type: storageType,
  			device_interface: deviceInterface,
  			serial_number: ( data['Serial No'] || null ),
  			manufacturer: ''+makeModel.getManufacturerSysID(),
  			model_id: ''+makeModel.getModelNameSysID(),
  			computer: deviceCi,
  			'interface': deviceInterface, // deprecated
  			drive_type: storageType // deprecated
  		});
  		size.applyToStorageDeviceSize(diskCi);

  		// associate the disk ci with the device ci.
  		deviceCi.addReferral(diskCi);

  		// append data for specific tables
  		switch (table) {
  		case 'cmdb_ci_iscsi_disk':
  			diskCi.data.iqn = iscsiDevice.targetIqn;
  			diskCi.data.device_lun = iscsiDevice.lun;
  			diskCi.data.initiator_iqn = results.sys_iscsi_initiator;

  			// attempt to relate to upstream volume on array
  			try {
  				storageReconciler.createISCSIDiskCiToVolumeRel(diskCi);
  			} catch (e) {
  				if (this.isDebugging())
  					this.warn(e);
  			}

  			break;
  		}

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

  /**
   * Processes partitions based on results returned from the 'prtvtoc' command.
   * Associates the new partition Ci to its corresponding disk Ci as well as the device Ci, both by reference.
   */
  _processPartitions: function(results, deviceCi) {
  	if (typeof results.prtvtoc === 'undefined') {
  		this.warn('No partition results were returned.');
  		return;
  	}

  	var ciSchema = this.getCiSchema();
  	var diskCis = deviceCi.getReferrals('cmdb_ci_storage_device');
  	var tocsData = this._parsePrtvtoc(results.prtvtoc);

  	for (var i = 0; i < tocsData.length; ++i) {
  		var tocData = tocsData[i];
  		var diskCi = this._findDiskCi(tocData.deviceId, diskCis);
  		if (diskCi === null) {
  			this.warn('Unable to find disk Ci for partition result: ' + tocData.deviceId);
  			continue;
  		}

  		for (var j = 0; j < tocData.partitions.length; ++j) {
  			var data = tocData.partitions[j];
  			var size = new StorageDataSize(data.sectorCount * tocData.bytesPerSector);
  			var partitionCi = ciSchema.createCi('cmdb_ci_disk_partition', {
  				name: diskCi.data.device_id + 's' + data.partition,
  				partition_number: data.partition,
  				start_offset: (data.firstSector * tocData.bytesPerSector),
  				end_offset: (data.lastSector * tocData.bytesPerSector),
  				computer: deviceCi,
  				disk: diskCi
  			});
  			size.applyToDiskPartitionSize(partitionCi);

  			deviceCi.addReferral(partitionCi);
  			diskCi.addReferral(partitionCi);

  			if (this.isDebugging())
  				Debug.logObject('Partition CI', partitionCi.toShallowObj());
  		}
  	}
  },

  _toBytes: function(sizeStr) {
  	var matches = sizeStr.match(/(\d+(?:\.\d+))(\w+)/);
  	if (!matches )
  		return 0;

  	var num = matches[1];
  	var unit = matches[2];

  	var multipliers = {
  		G: 1024 * 1024 * 1024,
  		GB: 1024 * 1024 * 1024,
  		M: 1024 * 1024,
  		MB: 1024 * 1024,
  		K: 1024,
  		KB: 1024,
  		b: 1
  	};

  	var size = parseFloat(num);
  	var multiplier = multipliers[unit];

  	if (isNaN(size) || typeof multiplier === 'undefined')
  		return 0;

  	return Math.round(size * multiplier);
  },

  _processZfsPools: function(results, deviceCi) {
  	if (typeof results.zpool_list === 'undefined') {
  		this.warn('No ZFS pool results were returned.');
  		return;
  	}

  	var ciSchema = this.getCiSchema();
  	var diskCis = deviceCi.getReferrals('cmdb_ci_storage_device');
  	var zfsPoolsData = this._parseZpoolList(results.zpool_list);

  	for (var i = 0; i < zfsPoolsData.length; ++i) {
  		var data = zfsPoolsData[i];

  		var size = new StorageDataSize(this._toBytes(data.size));
  		var rawFree = ('free' in data) ? data.free : data.avail; // data.avail for Solaris older than "10 9/10 release"
  		var free_space = new StorageDataSize(this._toBytes(rawFree));
  		var zfsPoolCi = ciSchema.createCi('cmdb_ci_storage_pool', {
  			name: data.name,
  			pool_id: data.name,
  			hosted_by: deviceCi
  		});
  		size.applyToStoragePoolSize(zfsPoolCi);
  		free_space.applyToStoragePoolFreeSpace(zfsPoolCi);

  		deviceCi.addReferral(zfsPoolCi);

  		if (this.isDebugging())
  			Debug.logObject('ZFS Pool CI', zfsPoolCi.toShallowObj());

  	}
  },

  _processZfsPoolMembers: function(results, deviceCi) {
  	if (typeof results.zpool_status === 'undefined') {
  		this.warn('No ZFS pool member results were returned.');
  		return;
  	}

  	var ciSchema = this.getCiSchema();
  	var diskCis = deviceCi.getReferrals('cmdb_ci_storage_device');
  	var partitionCis = deviceCi.getReferrals('cmdb_ci_disk_partition');
  	var poolCis = deviceCi.getReferrals('cmdb_ci_storage_pool');
  	var zfsPoolMembersData = this._parseZpoolStatus(results.zpool_status);

  	for (var poolName in zfsPoolMembersData) {
  		var data = zfsPoolMembersData[poolName];

  		var poolCi = this._findPool(poolName, poolCis);
  		if (poolCi === null) {
  			this.warn('ZFS Member pool Ci not found: ' + poolName);
  			continue;
  		}

  		var storageCi = this._findPoolMemberStorage(data.name, diskCis, partitionCis);
  		if (storageCi === null) {
  			if (this._descriptiveDiskNames)
  				this.warn('ZFS Member storage Ci not found: ' + data.name);
  			continue;
  		}

  		var zfsPoolMemberCi = ciSchema.createCi('cmdb_ci_storage_pool_member', {
  			pool: poolCi,
  			storage: storageCi
  		});

  		poolCi.addReferral(zfsPoolMemberCi);
  		storageCi.addReferral(zfsPoolMemberCi);

  		if (this.isDebugging())
  			Debug.logObject('ZFS Pool Member CI', zfsPoolMemberCi.toShallowObj());

  	}
  },

  /**
   * Processes file systems based on the results returned from the 'df' command.
   * Associates each new file system Ci with the deviceCi as well as with the providing partition Ci or pool Ci, both by reference.
   */
  _processFileSystems: function(results, deviceCi) {
  	if (typeof results.df === 'undefined') {
  		this.warn('No file system results were returned.');
  		return;
  	}

  	var ciSchema = this.getCiSchema();
  	var volumeToDiskMapper = new DiscoveryVolumeToDiskMapper({ debug: this.isDebugging() });
  	var storageReconciler = new DiscoveryStorageAllocationReconciler({ ciSchema: ciSchema });
  	var poolCis = deviceCi.getReferrals('cmdb_ci_storage_pool');
  	var partitionCis = deviceCi.getReferrals('cmdb_ci_disk_partition');
  	var fileSystemsData = this._parseDf(results.df);

  	for (var i = 0; i < fileSystemsData.length; ++i) {
  		var data = fileSystemsData[i];

  		var nasInfo = storageReconciler.getNASInfo(data.name);
  		var table, mediaType;
  		if (nasInfo) {
  		   table = "cmdb_ci_nas_file_system";   
  		   mediaType = "network";
  		}
  		else {
  		   table = "cmdb_ci_file_system";
  		   mediaType = "fixed";
  		}   

  		var providedByCi = this._findFileSystemProvider(data.name, partitionCis, poolCis);
  		var size = new StorageDataSize(( data.size || 0 ) * 1024);
  		var free_space = new StorageDataSize(( data.freeSpace || 0 ) * 1024);

  		var fileSystemCi = ciSchema.createCi(table, {
  			name: data.name,
  			mount_point: data.mountPoint,
  			media_type: mediaType,
  			provided_by: providedByCi,
  			computer: deviceCi,
  			nas_path: nasInfo ? nasInfo.path : null,
  			nas_hostname : nasInfo ? nasInfo.hostname : null,
  			nas_ip_address : nasInfo && nasInfo.ip ? nasInfo.ip : null
  			// TODO: Where to get nas_protocol?
  		});
  		size.applyToStorageVolumeSize(fileSystemCi);
  		free_space.applyToStorageVolumeFreeSpace(fileSystemCi);

  		deviceCi.addReferral(fileSystemCi);

  		if (providedByCi !== null) {
  			providedByCi.addReferral(fileSystemCi);
  			volumeToDiskMapper.relateStorageDevice(fileSystemCi);
  		}

  		if (this.isDebugging())
  			Debug.logObject('File System CI', fileSystemCi.toShallowObj());
  	}
  },

  /**
   * Reconciles any NAS file systems with its remote provider.
   * Called after() updating.
   */
  _reconcileNas: function(deviceCi) {
  	var deviceId = ''+this.getCmdbCi();

  	var reconciler = new DiscoveryStorageAllocationReconciler();
  	reconciler.reconcileFileSystemByServer(deviceId);	
  },

  /**
   * Transforms probe results into a hashmap of lines per sub-probe.
   * Sub-probes are defined by the marker: [[==PROBE:<subProbe>==]] where subProbe is the key of the results returned.
   * @param {} result The probe's result.output.
   * @return {string subProbe:string[] outputLines}
   */
  _parseResult: function(result) {
  	var splits = result.split(DiscoverySolarisStorageSensor._PROBE_DELIMITER_REGEX);
  	var results = {};

  	for (var i = 1; i < splits.length; i += 2) {
  		var key = splits[i];
  		if (key === 'iscsi_session') {
  			var parsed = new DiscoveryIndentedMarkup({ pluralRoot: true }).parse(splits[i+1].trim());
  			results[key] = ( gs.nil(parsed.iscsiSession) ? [] : parsed.iscsiSession );
  			break;
  		}

  		var lines = splits[i+1].trim().split('\n');

  		results[key] = [];
  		for (var j = 0; j < lines.length; ++j) {
  			var line = lines[j].trim();
  			line && results[key].push(line);
  		}
  	}

  	var initiator = ''+results.sys_iscsi_initiator[0];
  	if (initiator) {
  		initiator = initiator.split(':');
  		if (initiator) {
  			initiator.shift();
  			results.sys_iscsi_initiator = initiator.join(':').trim();
  		}
  	}

  	return results;
  },

  /**
   * @param string[] lines
   * @return {string solarisId:string linuxId}
   */
  _parseIostatDiff: function(lines) {
  	var map = {};

  	for (var i = 0; i < lines.length; i+=2)
  		map[lines[i+1].trim()] = lines[i].trim();

  	return map;
  },

  /**
   * Parses the 'iostat' command output and returns an hashmap representing
   *  each field/value for each device.
   * 
   *  Each device is represented by multiple lines.
   *  We combine each device's lines into one, then match regex against it
   *
   *  Typical input record:
   *  
   *  c4t0d0           ,Soft Errors: 0 ,Hard Errors: 0 ,Transport Errors: 0 
   *  Vendor: VMware   ,Product: Virtual disk     ,Revision: 1.0  ,Serial No:  
   *  Size: 21.47GB &lt;21474836480 bytes&gt;
   *  ,Media Error: 0 ,Device Not Ready: 0 ,No Device: 0 ,Recoverable: 0 
   *  Illegal Request: 0 ,Predictive Failure Analysis: 0
   *
   *  Field format - "Field Name: Field Value ," or "Field Name: Field Value\n"
   *
   * @param string[] lines
   * @return {string fieldName:string value}[]
   */
  _parseIostat: function(lines) {
  	var ioStatRecordHdrRegex = /^((c|s)\w+)\s+,/;
  	var records = [];
  	var curRecord = {};

  	// Walk backward, parsing lines into fields until next record boundary
  	while (lines.length) {
  		var curLine = lines.pop();
  		var fields = curLine.split(',');
  		for (var i = 0; i < fields.length; i++) {
  			var field = fields[i].split(':');
  			if (field && field.length == 2)
  				curRecord[field[0].trim()] = field[1].trim();
  		}

  		// When we find a record boundary, output and clear collected fields
  		var hdrMatches = curLine.match(ioStatRecordHdrRegex);
  		if (hdrMatches) {
  			curRecord.deviceId = hdrMatches[1];
  			records.push(curRecord);
  			// PRB716750: On some virtualized server "iostat -n" does not return the descriptive
  			// names and therefore will not be found when processing pool members.  In that case
  			// there's not much we can do, so just disable the warnings.
  			// The regex probably can be simplified, requires more test though.
  			var descriptiveRegex = /(([cdts][0-9|A-F|a-f]+)*([cdts][0-9|A-F|a-f])+)/;
  			var matches = curRecord.deviceId.match(descriptiveRegex);
  			if (!matches[0].equals(curRecord.deviceId)) {
  				this._descriptiveDiskNames = false;
  			}
  			curRecord = {};
  		}
  	}

  	if (this.isDebugging())
  		Debug.logObject('iostat Data', records);

  	return records;
  },

  /**
   * Parses the 'prtvoc' command output and returns an hashmap representing each field/value for each partition.
   *
   ** Example results:
   *   [0]: Object
   *     deviceId: string = c5t6d0
   *     bytesPerSector: string = 512
   *     partitions: Array of 2 elements
   *     [0]: Object
   *       lastSector: string = 4186111
   *       mountDirectory: null = /mnt/foobar
   *       firstSector: string = 0
   *       sectorCount: string = 4186112
   *       partition: string = 2
   *       flags: string = 01
   *       tag: string = 5
   *     [1]: Object
   *       lastSector: string = 4095
   *       mountDirectory: null = null
   *       firstSector: string = 0
   *       sectorCount: string = 4096
   *       partition: string = 8
   *       flags: string = 01
   *       tag: string = 1
   **
   * @param string[] lines
   * @return {string fieldName:string value}[]
   */
  _parsePrtvtoc: function(lines) {
  	var results = [];
  	// { string deviceId:string[] lines }
  	var deviceLineMap = {};

  	// build deviceLineMap. each value will be an array of lines corresponding to that deviceId
  	var currentDeviceId = null;
  	var currentDeviceLines = [];
  	for (var i = 0; i < lines.length; ++i) {
  		var line = lines[i];

  		// attempt to find the opening "* /dev/rdsk/<deviceId> partition map" line
  		var matches = line.match(/^\*\s+\/dev\/rdsk\/(\w+)/);
  		if (matches !== null) {
  			// store the previous set of lines if this isn't the first iteration
  			if (currentDeviceId !== null)
  				deviceLineMap[currentDeviceId] = currentDeviceLines;

  			currentDeviceId = matches[1].trim();
  			currentDeviceId = currentDeviceId.substring(0, currentDeviceId.length - 2); // remove the trailing slice number 's2'
  			currentDeviceLines = []; // skip the opening line, not needed
  		} else {
  			currentDeviceLines.push(line);
  		}
  	}

  	// push the last set
  	if (currentDeviceId !== null)
  		deviceLineMap[currentDeviceId] = currentDeviceLines;

  	for (var deviceId in deviceLineMap) {
  		var deviceLines = deviceLineMap[deviceId];
  		var data = { deviceId: deviceId };

  		for (var i = 0; i < deviceLines.length; ++i) {
  			var line = deviceLines[i];

  			// find the bytes per sector from the Dimensions: table
  			if (typeof data.bytesPerSector === 'undefined') {
  				var matches = line.match(/^\*\s+(\d+) bytes\/sector/);
  				if (matches !== null)
  					data.bytesPerSector = ( parseInt(matches[1].trim()) || 0 );

  				continue;
  			// next, find the partition table
  			} else if (typeof data.partitions === 'undefined') {
  				var matches = line.match(/^\*\s+Partition/);
  				if (matches !== null)
  					data.partitions = [];

  				continue;
  			// process the partition table
  			} else {
  				// find a data line. line has already been trimmed, so no leading \s+
  				if (!line.match(/^\d+\s+/))
  					continue;

  				// columns are separated by whitespace
  				var columns = line.split(/\s+/);
  				var partitionData = {
  					partition: columns[0],
  					tag: columns[1],
  					flags: columns[2],
  					firstSector: (parseInt(columns[3]) || 0),
  					sectorCount: (parseInt(columns[4]) || 0),
  					lastSector: (parseInt(columns[5]) || 0),
  					mountDirectory: columns[6] || null
  				};

  				data.partitions.push(partitionData);
  			}
  		}

  		results.push(data);
  	}

  	if (this.isDebugging())
  		Debug.logObject('prtvtoc Results', results);

  	return results;
  },

  /**
   * Parses the 'zpool list' command output and returns an hashmap representing each field/value for each zpool.
   *
   ** Example results
   * [0]: Object
   *   free: string = 14.2G  (prop named "avail" in Solaris older than "10 9/10 release"
   *         per per http://docs.oracle.com/cd/E19253-01/819-5461/gjygg/index.html)
   *   dedup: string = 1.00x
   *   health: string = ONLINE
   *   altroot: string = -
   *   size: string = 19.9G
   *   alloc: string = 5.69G  (prop name "used" in Solaris older than "10 9/10 release"
   *          per http://docs.oracle.com/cd/E19253-01/819-5461/gjygg/index.html)
   *   name: string = rpool
   *   cap: string = 28%
   **
   * @param string[] lines
   * @return {string fieldName:string value}[]
   */
  _parseZpoolList: function(lines) {
  	if (lines.length < 2)
  		return [];

  	var results = [];

  	// parse column headers on first line
  	var headerLine = lines[0];
  	var headers = headerLine.split(/\s+/);
  	if (headers === null)
  		return [];

  	// convert to lower case
  	for (var i = 0; i < headers.length; ++i)
  		headers[i] = headers[i].toLowerCase();

  	for (var i = 1; i < lines.length; ++i) {
  		var columns = lines[i].split(/\s+/);
  		var data = {};
  		for (var h = 0; h < headers.length; ++h)
  			data[headers[h]] = columns[h];

  		results.push(data);
  	}

  	if (this.isDebugging())
  		Debug.logObject('zpool list Results', results);

  	return results;
  },

  /**
   * Parses the 'zpool status' command output and returns an hashmap representing each field/value for each zpool member.
   *
   ** Example result
   * Object:
   *   rpool: Object
   *     read: string = 0
   *     state: string = ONLINE
   *     write: string = 0
   *     name: string = c4t0d0s0
   *     cksum: string = 0
   **
   * @param string[] lines
   * @return {string poolId:{string fieldName:string value}}
   */
  _parseZpoolStatus: function(lines) {
  	if (lines.length < 2)
  		return {};

  	var results = {};

  	// parse column headers on first line
  	var headerLine = lines[0];
  	var headers = headerLine.split(/\s+/);
  	if (headers === null)
  		return [];

  	// convert to lower case
  	for (var i = 0; i < headers.length; ++i)
  		headers[i] = headers[i].toLowerCase();

  	var currentPoolId = null;

  	for (var i = 1; i < lines.length; ++i) {
  		var columns = lines[i].split(/\s+/);

  		// if it's a pool name, update the current id and continue;
  		var matches = columns[0].match(/^c\w+/);
  		if (matches === null) {
  			currentPoolId = columns[0];
  			continue;
  		}

  		var data = {};
  		for (var h = 0; h < headers.length; ++h)
  			data[headers[h]] = columns[h];

  		results[currentPoolId] = data;
  	}

  	if (this.isDebugging())
  		Debug.logObject('zpool status Results', results);

  	return results;
  },

  _parseDf: function(lines) {
  	var results = [];

  	for (var i = 0; i < lines.length; ++i) {
  		var line = lines[i];

  		// if this line was split due to length, combine this line with the following
  		if (line.indexOf(' ') === -1 && (i + 1) < lines.length && lines[(i + 1)].match(/^\d+/)) {
  			line = line + ' ' + lines[(i+1)].trim();
  			++i;
  		}

  		// neither local nor remote (cifs/nfs)
  		if (!line.startsWith('/dev/') && !line.startsWith('//') && !line.match(/^\w+\/\w+/) && line.indexOf(':') === -1)
  			continue;

  		var columns = line.split(/\s+/);
  		var data = {
  			name: columns[0],
  			size: columns[1],
  			freeSpace: columns[3],
  			mountPoint: columns[5]
  		};

  		results.push(data);
  	}

  	if (this.isDebugging())
  		Debug.logObject('df Results', results);

  	return results;
  },

  /**
   * @param string solarisDeviceId
   * @return string|null The legacy "linux" device id (sd0, sr1, etc.) for the given "Solaris" device id (c0t1d0s2, c1t0d0s5, etc.) or null if not found.
   */
  _findLinuxDeviceId: function(solarisDeviceId) {
  	return ( this._deviceIdMap[solarisDeviceId] || null );
  },

  /**
   * @param partitionName The solaris partition/slice id
   * @param Ci[] An array of disk Cis to search against
   * @return Ci|null A disk Ci or NULL if not found.
   */
  _findDiskCi: function(deviceId, diskCis) {
  	for (var i = 0; i < diskCis.length; ++i) {
  		var diskCi = diskCis[i];
  		if (diskCi.data.device_id === deviceId)
  			return diskCi;
  	}

  	return null;
  },

  _findPool: function(poolId, poolCis) {
  	for (var i = 0; i < poolCis.length; ++i) {
  		var poolCi = poolCis[i];
  		if (poolCi.data.pool_id === poolId)
  			return poolCi;
  	}

  	return null;
  },

  _findPartition: function(diskId, partitionNumber, partitionCis) {
  	for (var i = 0; i < partitionCis.length; ++i) {
  		var partitionCi = partitionCis[i];
  		var diskCi = partitionCi.data.disk;

  		if (diskCi.data.device_id === diskId && partitionCi.data.partition_number === partitionNumber)
  			return partitionCi;
  	}

  	return null;
  },

  _findPartitionByName: function(name, partitionCis) {
  	for (var i = 0; i < partitionCis.length; ++i) {
  		var partitionCi = partitionCis[i];
  		if (partitionCi.data.name == name)
  			return partitionCi;
  	}

  	return null;
  },

  _findFileSystemProvider: function(fileSystemName, partitionCis, poolCis) {
  	// match against ZFS pools
  	var matches = fileSystemName.match(/^(\w+)\/\w+/);
  	if (matches !== null) {
  		var poolName = matches[1];
  		var poolCi = this._findPool(poolName, poolCis);
  		if (poolCi !== null)
  			return poolCi;
  	}

  	// match against partitions
  	var matches = fileSystemName.match(/^\/dev\/(?:dsk\/)?(.+)$/);
  	if (matches != null) {
  		var partitionCi = this._findPartitionByName(matches[1], partitionCis);
  		if (partitionCi !== null)
  			return partitionCi;
  	}

  	return null;
  },

  _findPoolMemberStorage: function(storageDeviceId, diskCis, partitionCis) {
  	// match against partition
  	var matches = storageDeviceId.match(/(\w+)s(\d+)$/);
  	if (matches !== null) {
  		var deviceId = matches[1];
  		var partitionNumber = matches[2];
  		var partitionCi = this._findPartition(deviceId, partitionNumber, partitionCis);
  		if (partitionCi !== null)
  			return partitionCi;
  	}

  	// match against disk
  	var matches = storageDeviceId.match(/(\w+)$/);
  	if (matches !== null) {
  		var deviceId = matches[1];
  		var diskCi = this._findDiskCi(deviceId, diskCis);
  		if (diskCi !== null)
  			return diskCi;
  	}

  	return null;
  },

  /**
   * @param {} results The parsed probe results
   * @param string deviceId
   * @return {}|null Returns an iscsi session hashmap parsed from the probe results
   */
  _findIscsiDevice: function(results, deviceId) {
  	for (var i = 0; i < results.iscsi_session.length; ++i) {
  		var iscsiSession = results.iscsi_session[i];

  		var devices = g_array_util.ensureArray(iscsiSession.device);
  		for (var j = 0; j < devices.length; j++) {
  			var device = devices[j];
  			var sessionDeviceId = device.id;
  			// remove the slice number
  			sessionDeviceId = sessionDeviceId.substring(0, sessionDeviceId.length - 2);
  			if (sessionDeviceId === deviceId) {
  				device.targetIqn = iscsiSession.targetIqn;
  				return device;
  			}
  		}
  	}

  	return null;
  },
  
  
  /**
   * Parses the 'fcinfo hba-port' command output  
   **/
  _processHbaInfo: function(results, deviceCi) {
  	
  	if (typeof results.fcinfo_hba === 'undefined') {
  		this.warn ('No HBA results were returned.');
  		return;
  	}

  	if (results.fcinfo_hba.length == 1 && results.fcinfo_hba[0].indexOf ("No Adapters Found") !== -1){
  		this.warn ('No Adapters Found.');
  		return;
  	}

  	// Create a list of ports on the hba. For every port we create an object containing all discovered information. 
  	var portInfo = results.fcinfo_hba; 
  	var portList = {};
  	var curPwwn = null; 
  	for (var i = 0 ; i < portInfo.length ; i++){
  		// Expected output looks like:
  		//   HBA Port WWN: 230008008493850
  		//       OS Device Name: /dev/cfg/c5
  		//       Manufacturer: Emulex
  		//       Model: LP10000DC-S
  		//       Firmware Version: 1.92a1 (T2D1.92A1)
  		//  ...etc...
  		var info = portInfo[i].split(":");
  		// Ignore unexpected output
  		if (info.length < 2)
  			continue;
  		var name = info[0];
  		var value = info [1].trim();  
  		if (name.indexOf("HBA") === 0){
  			portList[value] = {}; 
  			curPwwn = value;
  		} else if (curPwwn) {
  			// for consistancy we convert the name to lowercase and replace " " to "_" 
  			name = name.toLowerCase();
  			name = name.replace(/ /g,"_"); 
  			portList[curPwwn][name] = value;
  		}
  	}
  	
  	// Process HBA information
  	this._processFCAdapters (portList, deviceCi); 
  	this._processFCPorts (portList, deviceCi); 
  	this._processFCDisks (results, deviceCi); 	
  },
  
  /**
   * Process 'fcinfo hba-port' command output and create a Ci for every discovered HBA.
   **/
  _processFCAdapters: function(portList, deviceCi) {
  	var ciSchema = this.getCiSchema();
  	var adapterCis = []; 
  	
  	for (var port in portList) {
  		var portInfo = portList[port]; 
  		
  		// Create HBA card based on the serial_number. Different WWNNs are possible on a dual channel HBA
  		// Search if we already have the hba
  		var found = false; 
  		for (var j = 0; j < adapterCis.length; ++j) {
  			if (portInfo.serial_number && adapterCis[j].data.serial_number === portInfo.serial_number ) {
  				found = true;
  				break;
  			}
  		}
  		
  		if (found)
  			continue;
  		
  		
  		var makeModel = MakeAndModelJS.fromNames(portInfo.manufacturer, portInfo.model, 'hardware');
  		var theSerialNumber = portInfo.serial_number || "";
  		
  		// Create new Ci for the hba
  		var adapterCi = ciSchema.createCi('cmdb_ci_storage_hba', {
  			computer: deviceCi,
  			device_id: theSerialNumber,
  			manufacturer: ''+ makeModel.getManufacturerSysID(),
  			model_id: ''+ makeModel.getModelNameSysID(),
  			serial_number: theSerialNumber,
  			name: portInfo.manufacturer.split(/ /)[0] + ' ' + theSerialNumber
  		});

  		deviceCi.addReferral(adapterCi);
  		adapterCis.push(adapterCi); 
  	}
  	
  },
  
  /**
   * Process 'fcinfo hba-port' command output and populate port information for every HBA.
   **/
  _processFCPorts: function(portList, deviceCi) {
  	var ciSchema = this.getCiSchema();
  	var adapterCis = deviceCi.getReferrals('cmdb_ci_storage_hba');
  	
  	for (var pKey in portList) {
  		var port = portList[pKey];
  		var wwnn = new StorageWWN(port.node_wwn).toString();
  		var wwpn = new StorageWWN(pKey).toString();
  		
  		// find the corresponding HBA using serial_number 
  		var adapterCi = null;
  		for (var j = 0; j < adapterCis.length; j++) 
  			if (port.serial_number  === adapterCis[j].data.serial_number){
  				adapterCi = adapterCis[j];
  				break; 
  			}
  		

  		// skip this port if we unexpectedly cannot find the hba
  		if (adapterCi === null) {
  			this.warn('Unable to find FC HBA for FC Port: ' + wwpn);
  			continue;
  		}

  		
  		var portType = new DiscoveryFcPortType(port.type.split(/_/)[0]).toString(); // "N_Port" --> "N"
  		
  		var speed = null;
  		try {
  			if (port.current_speed.length !== 0)
  				speed = new DiscoveryDataRate(port.current_speed, 'GFC');
  		} catch (e) { 
  			if (this.isDebugging())
  					this.warn(e);
  		}
  		
  		// Create a new Ci for the port
  		var portCi = ciSchema.createCi('cmdb_ci_fc_port', {
  			computer: deviceCi,
  			controller: adapterCi,
  			wwpn: wwpn,
  			wwnn: wwnn,
  			port_type: portType,
  			speed: ( speed ? speed.to(DiscoveryDataRate.Units.GFC) + ' GFC' : null ),
  			operational_status: ( port.state === 'online' ? 1 : 0 ),
  			name: 'FC Port ' + wwpn
  		});

  		deviceCi.addReferral(portCi);
  		adapterCi.addReferral(portCi);

  	}
  	
  },
  /**
   * 
   * Process 'fcinfo remote-port -slp $port' command output for every existing port on the hbas.
   * For every port we collect some  information like 
   *
   *  **Remote ports for: 2100001b329b2649
   *
   *	Remote Port WWN: 500601683de02ca6
   *	Active FC4 Types: SCSI
   * 	SCSI Target: yes
   *	Node WWN: 50060160bde02ca6
   *	LUN: 0
   *		Vendor: DGC
   *		Product: LUNZ
   *		OS Device Name: /dev/rdsk/c6t500601683DE02CA6d0s2
   *
   *  
   **/
  _processFCDisks: function(results, deviceCi) {
  	
  	if (typeof results.fcinfo_target === 'undefined') {
  		this.warn ('No remote ports were returned for HBA.');
  		return;
  	}
  	
  	var hostPort = null; 
  	var remotePort = {}; 
  	
  	for (var i = 0 ; i < results.fcinfo_target.length; i++){
  		
  		var targetInfo = results.fcinfo_target[i].split(":"); 
  		// for consistancy we convert the key to lowercase and replace " " to "_" 
  		var key = targetInfo[0].toLowerCase();
  		key = key.replace(/ /g, "_");
  		var value = targetInfo[1].trim();
  		 
  		// Port on the host for that we run "fcinfo remote-port" 
  		if (key.indexOf("**remote_ports_for") !== -1){
  			hostPort = new StorageWWN(value).toString();
  			continue;
  		}
  		
  		// Collect all necessary information for every remote_port 
  		switch (key){
  			case "remote_port_wwn": 
  				// begining of the new remote-port, and time to process information for the previous remote-port if it was target. 
  				if (JSUtil.notNil(remotePort.isTarget) && remotePort.isTarget)
  					this._processRemotePorts(remotePort, hostPort, deviceCi);
  
  				remotePort = {};
  				remotePort.luns = []; 
  				remotePort.wwpn = new StorageWWN(value).toString(); 
  				break;
  			case "scsi_target" :
  				remotePort.isTarget = ( value === 'yes' ? true : false);
  				break;
  			case "node_wwn":
  				remotePort.wwnn = new StorageWWN(value).toString();
  				break;
  			case "lun":
  				var lunInfo = {};
  				lunInfo.number = value;
  				remotePort.luns.push(lunInfo);
  				break;
  			case "os_device_name":
  				remotePort.luns[remotePort.luns.length-1].devName = value; 
  				break; 
  		}
  		
  		
  	}
  },
  
  
  /**
   * Process the information for a remote_port and create corresponding Cis.
   * @param {} rmPort An object containing all information for aremote-port
   * @param hPort  The host port connected to the rmPort
   * @param deviceCi
   **/
  _processRemotePorts: function(rmPort, hPort, deviceCi) {
  	
  	var ciSchema = this.getCiSchema();
  	var storageReconciler = new DiscoveryStorageAllocationReconciler({ ciSchema: ciSchema });
  	var diskCis = deviceCi.getReferrals('cmdb_ci_storage_device');
  	var portCis = deviceCi.getReferrals('cmdb_ci_fc_port');
  	 
  	var portCi = null;
  	var diskCi = null;
  	for (var i = 0; i < portCis.length; i++) 
  		if (portCis[i].data.wwpn === hPort){
  			portCi = portCis[i];
  			continue;
  		}
  	
  	if (portCi === null){
  		this.warn ( "No port defined on HBA for " + hPort); 
  		return;
  	} 
  	
  	for ( i = 0; i < rmPort.luns.length; i++){
  		diskCi = null; 
  		// For every lun, the last part of os_device_name includes the disk name. 
  		// for example /dev/rdsk/c6t500601683DE02CA6d0s2 is realted to disk_id 
  		// "c6t500601683DE02CA6d0" (s2 at the end of the diskname means the entire disk). 
  		var diskName = rmPort.luns[i].devName.split("/").pop();
  		for (var j = 0 ; j < diskCis.length; j++) 
  			if (diskName.indexOf(diskCis[j].data.device_id) === 0){
  				diskCi = diskCis[j];
  				continue;
  			}
  		
  		if (diskCi === null){
  			this.warn("Unable to match FC port to device_id: " + diskName); 
  			continue;
  		}
  		
  		diskCi.table = 'cmdb_ci_fc_disk';
  		diskCi.data.storage_type = 'network';
  		diskCi.data.device_interface = 'fc';
  		diskCi.data.device_lun = rmPort.luns[i].number;
  		diskCi.data.provided_by = portCi;
  		
  		portCi.addReferral(diskCi);
  		// populate wwnn and wwpn for the target path in cmdb_fc_target
  		var targetWWPN = StorageWWN.parse(rmPort.wwpn);
  		var targetWWNN = StorageWWN.parse(rmPort.wwnn);
  		var targetCi = this.createTargetCi(targetWWPN, targetWWNN); 
  		
  		diskCi.addReferral(targetCi);
  		
  		var initiatorCi = ciSchema.createCi('cmdb_fc_initiator', { 
  					wwnn: portCi.data.wwnn,
  					wwpn: portCi.data.wwpn
  				});
  		diskCi.addReferral(initiatorCi);
  		
  		// attempt to relate to upstream volume on array
  		try {
  			storageReconciler.createFCDiskCiToVolumeRel(diskCi, new Array(targetWWPN), portCi.data.wwpn);
  		} catch (e) {
  			if (this.isDebugging())
  					this.warn(e);
  		}
  		
  	}
  	
  },
  
  // Create a Ci including wwnn, wwpn information for a path to the fc disk from this solaris server
  createTargetCi: function(wwnn, wwpn){
  	var ciSchema = this.getCiSchema();
  	var wwCi = ciSchema.createCi('cmdb_fc_target', { 
  			wwnn: wwnn,
  			wwpn: wwpn
  	});
  	 
  	return wwCi;  
  },
  type: 'DiscoverySolarisStorageSensor'
});

Sys ID

1b5ae47037222100dcd445cbbebe5dce

Offical Documentation

Official Docs: