UNPKG

on-http-y1

Version:
337 lines (311 loc) 12.1 kB
#!/usr/bin/env node 'use strict'; var exec = require('child_process').exec; var execSync = require('child_process').execSync; var cmdDriveWwid = 'ls -l /dev/disk/by-id'; var cmdVdInfo = 'ls -l /dev/disk/by-path'; var cmdScsiId = 'lsscsi'; var cmdSataRawInfo = 'sudo hdparm --Istdout'; var options = { timeout: 10000 //10 seconds }; /** * Get SATA drive's logic unit name * @param {String} sataDrive SATA drive's name, eg. 'sda' * @return {String} SATA drive's logic unit name with 60 characters like: * 'SATADOM2-ML_3SE_________________________20160623AA106710169E' */ function getSataNameStr(sataDrive) { var output = execSync(cmdSataRawInfo + ' /dev/' + sataDrive); /* output data like below: * $ sudo hdparm --Istdout /dev/sda * dev/sda: * * 0040 3fff 0000 0010 7e00 0200 003f 0000 * 0000 0000 514d 3030 3030 3520 2020 2020 * 2020 2020 2020 2020 0003 0200 0004 322e * 322e 3120 2020 5145 4d55 2048 4152 4444 * 4953 4b20 2020 2020 2020 2020 2020 2020 * 2020 2020 2020 2020 2020 2020 2020 8010 * ..... * * Among those raw data: * Serial number hex string is from Word 10 to 19, total 20 bytes * Model number hex string is from Word 27 to 46, total 40 bytes */ var lines = output.toString().split('\n'); var hexLineMatch = /^([0-9A-Fa-f]{4}\s+){7}[0-9A-Fa-f]{4}$/; var rawHexStr = lines.reduce(function (result,line) { if(hexLineMatch.test(line)) { result.push(line); } return result; },[]).join(' ').split(' '); //Logic unit name string should be: Model Number String + Serial Number String var snHexStr = rawHexStr.slice(10, 20).join(''); var modelHexStr = rawHexStr.slice(27, 47).join(''); var nameHexStr = modelHexStr + snHexStr; var nameStr = ''; for(var i = 0; i < nameHexStr.length; i+=2) { var ascii = Number('0x' + nameHexStr.charAt(i) + nameHexStr.charAt(i+1)); var nameChar = String.fromCharCode(ascii); if(nameChar === ' ') { nameStr += '_'; // All space should be filled with '_' } else if(nameChar === '\u0000') { // ASCII code 0x00 is parsed as nameChar '\u0000' nameStr += '00'; // ESXi parses ASCII code 0x00 as string '00' } else { nameStr += nameChar; } } return nameStr; } /** * Parse the Drive WWID output * @param {String} idList - stdout for command 'ls -l /dev/disk/by-id' * @return {Object} include device name, Linux and ESXi WWIDs for each disks */ function parseDriveWwid(idList) { //idList example //"lrwxrwxrwx. 1 root root 9 Nov 18 20:30 ata-SATADOM-SV_3SE_20150522AA9992050085 -> ../../sdb" //returned string example // "ata-SATADOM-SV_3SE_20150522AA9992050085->../../sdb" var lines = idList.split('\n').map(function(line) { var split = line.split(/\s+/); return [split[8],split[10]].join('->'); }); //According to SCSI-3 spec, vendor specified logic unit name string is 60 //Only IDE and SCSI disk will be retrieved var scsiLines = [], sataLines = [], wwnLines = [], usbLines = []; lines.forEach(function(line){ if ( line && !(line.match('part')) && line.match(/[sh]d[a-z]?[a-z]$/i)){ var nameIndex = line.lastIndexOf('/'), idIndex = line.lastIndexOf('->'); if (line.indexOf('scsi') === 0) { scsiLines.push([line.slice(nameIndex + 1), line.slice(0, idIndex)]); } else if (line.indexOf('ata') === 0) { sataLines.push([line.slice(nameIndex + 1), line.slice(0, idIndex)]); } else if (line.indexOf('wwn') === 0) { wwnLines.push([line.slice(nameIndex + 1), line.slice(0, idIndex)]); } else if (line.indexOf('usb') === 0) { usbLines.push([line.slice(nameIndex + 1), line.slice(0, idIndex)]); } } }); //wwnLine example: wwn-0x5000cca23de9e287 -> ../../sdb //esxiWwn example: ["sdb", "naa.5000cca23de9e287"] var esxiWwn = wwnLines.map(function(wwnLine) { var line = wwnLine[1]; var split = line.split(/-/); return [wwnLine[0], 'naa.' + split[1].slice(2)]; }); //ESXi SATA WWID should be ('t10.ATA_____' + logic unit name) //ESXi SATA WWID should finally replace remaing dashs '-' with '2D', '3D' ..., 'ND', N means //it is the Nth dash. //esxiLine example: //["sda", "ata-32GB_SATA_Flash_Drive_B061430580090000000F"] //esxiSata example //["sda", "t10.ATA_____32GB_SATA_Flash_Drive___________________B061430580090000000F"] //TODO: confrim above analysis is correct for all SATA disks. var esxiSata = sataLines.map(function(esxiLine) { var line = esxiLine[1]; var headIndex = line.indexOf('-'); var headStr = ['t10.', line.slice(0, headIndex).toUpperCase(), '_____'].join(''), nameStr = getSataNameStr(esxiLine[0]); var strLine = headStr + nameStr; //return strLine.replace('-', '2D'); //ESXi driveid replaces Nth '-' with 'ND' like 2D, 3D var strArray = strLine.split('-'); if (strArray.length !== 1) { strLine = strArray[0]; for (var i = 0; i < strArray.length -1; i += 1){ strLine = [strLine, (i + 2).toString(), 'D', strArray[i + 1]].join(''); } } return [esxiLine[0], strLine]; }); //If one device Name is mapped to both WWN and SATA array, use WWN element instead esxiWwn.forEach(function(line) { esxiSata.forEach(function(subline){ if (line[0] === subline[0]){ subline [1] = line[1]; } }); }); //esxiLine example: ["sda", "scsi-35000c500725f45d7"] //esxiScsi example: ["sda", "naa.5000c500725f45d7"] var esxiScsi = scsiLines.map(function(esxiLine) { var line = esxiLine[1]; var split = line.split(/-|_/); return [esxiLine[0], 'naa.' + split[1].slice(1)]; }); var esxiUsb = usbLines.map(function(esxiLine) { return [esxiLine[0], 'mpx.']; }); //linuxLine example: ["sda", "scsi-35000c500725f45d7"] //linuxParsed example: ["sda", "/dev/disk/by-id/scsi-35000c500725f45d7"] var linuxParsed = sataLines.concat(scsiLines, usbLines).map(function(linuxLine) { return [linuxLine[0], '/dev/disk/by-id/' + linuxLine[1]]; }); return {esxiDriveIds: esxiSata.concat(esxiScsi, esxiUsb), linuxDriveIds: linuxParsed}; } /** * Parse Virtual Drive output via by-path output * @param {String} pathList - stdout for command 'ls -l /dev/disk/by-path' * @return {Array} include device name, virtual disk name and scsi ID for each disk */ function parseVdInfo(pathList) { /** * ls -l /dev/disk/by-path output example: * total 0 * lrwxrwxrwx 1 root root 9 Dec 30 11:55 pci-0000:00:10.0-scsi-0:0:0:0 -> ../../sda * lrwxrwxrwx 1 root root 10 Dec 30 11:55 pci-0000:00:10.0-scsi-0:0:0:0-part1 -> ../../sda1 */ var lines = pathList.split('\n').map(function(line) { var split = line.split(/\s+/); return [split[8],split[10]].join('->'); }); var pciLines = []; lines.forEach(function(line){ if ( line && !(line.match('part'))){ var nameIndex = line.lastIndexOf('/'), idIndex = line.lastIndexOf('->'); if (line.indexOf('pci') === 0) { pciLines.push([line.slice(nameIndex + 1), line.slice(0,idIndex)]); } } }); return pciLines.map(function(line) { var scsiId = line[1].slice(line[1].lastIndexOf('-') + 1), vdStr = ''; var scsiIdArray = scsiId.split(':'); if (scsiIdArray.length === 4){ //Suppose controller id = 0 stands for JBOD if (scsiIdArray[1] !== "0") { vdStr = ['/c', scsiIdArray[0], '/v', scsiIdArray[2]].join(''); } } return [line[0], vdStr, scsiId]; }); } /** * Parse scsi ID output via lsscsi command * @param {String} lsscsiList - stdout for command 'lsscsi' * @return {Array} include device name and scsi info for each disk */ function parseScsiInfo(lsscsiList) { /** * lsscsi output example: * [2:0:0:0] disk VMware Virtual disk 1.0 /dev/sda */ var lines = lsscsiList.split('\n'); return lines.map(function(line) { if(line){ return [line.slice(line.lastIndexOf('/') + 1).replace(' ', ''), line.slice(1, line.indexOf(']'))]; } }); } /** * Build the drive mapping table * @param {String} wwidData * @param {String} vdData * @param {String} scsiData * @return 0 if success, otherwise failed. */ function buildDriveMap(wwidData, vdData, scsiData) { var parsedWwids = parseDriveWwid(wwidData), scsiList = parseScsiInfo(scsiData), vdList = parseVdInfo(vdData); var linuxWwids = parsedWwids.linuxDriveIds, esxiWwids = parsedWwids.esxiDriveIds; var driveIds=[]; esxiWwids.forEach(function(esxiWwid){ var vd = '', scsiId = '', diskPath = esxiWwid[0]; vdList.forEach(function(elem){ if (typeof elem !== 'undefined') { if (elem[0] === diskPath) { vd = elem[1]; } } }); scsiList.forEach(function(elem){ if (typeof elem !== 'undefined'){ if(elem[0] === diskPath){ scsiId = elem[1]; } } }); //vmhbaAdapter:CChannel:TTarget:LLUN //Adapter is set to default value 32 for only one eUsb if (esxiWwid[1] === "mpx.") { esxiWwid[1] = ''; if (scsiId !== '') { var hctl = scsiId.split(":"); if (hctl.length === 4) { esxiWwid[1] = "mpx.vmhba32:C".concat(hctl[1], ":T", hctl[2], ":L", hctl[3]); } } vd = ''; } driveIds.push({"scsiId": scsiId, "virtualDisk": vd, "esxiWwid": esxiWwid[1], "devName": diskPath}); }); linuxWwids.forEach(function(linuxWwid, k){ driveIds[k].identifier = k; driveIds[k].linuxWwid = linuxWwid[1]; }); return JSON.stringify(driveIds); } /** * Run commands and notify result via callback * @param {Function} done - The callback which will be used to notify the result */ function run(done) { var wwidData, vdData, scsiData; try { exec(cmdDriveWwid, options, function (err0, stdout0) { if (err0) { return done(err0); } wwidData = stdout0; exec(cmdVdInfo, options, function (err1, stdout1) { if (err1) { var errStr= "ls: cannot access /dev/disk/by-path: No such file or directory"; //"/dev/disk/by-path" doesn't exist means only SATADOM exists, no HDDs // Ignore this error and use null string for vdData if (err1.toString().search(errStr) !== -1){ vdData = ''; } else { return done(err1); } } else { vdData = stdout1; } exec(cmdScsiId, options, function (err2, stdout2) { if (err2) { return done(err2); } scsiData = stdout2; var result = buildDriveMap(wwidData, vdData, scsiData); return done(null, result); }); }); }); } catch (e) { return done(e); } } if (require.main === module) { run(function(err, result) { if (err) { console.error(err.toString()); process.exit(1); } else { console.log(result); process.exit(0); } }); }