Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CD-582 - Add Power Usage info to old Dell iDRAC PSU Monitoringscript #391

Open
wants to merge 1 commit into
base: rc
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 169 additions & 97 deletions examples/ssh/dell_idrac_psu_info.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Name: Dell iDRAC PSU Monitoring
* Description: Monitors the operational status of Power Supply Units (PSUs) on a Dell server with iDRAC
* Description: Monitors the operational status of Power Supply Units (PSUs), as well as power usage details including total power consumption and capacity usage, on a Dell server with iDRAC.
*
* Communication protocol is SSH.
*
Expand All @@ -9,67 +9,184 @@
* Keyboard Interactive option: true/false (depends on iDRAC version).
*
* Creates a Custom Driver Table with the following columns:
* - Description
* - Primary Status
* - Total Output Power
* - Input Voltage
* - Redundancy Status
* - Part Number
* - Model
* - Manufacturer
* - Description: Description of the PSU unit
* - Primary Status: Current status of the PSU
* - Total Output Power: Total power output in watts (W)
* - Input Voltage: Voltage input to the PSU in volts (V)
* - Redundancy Status: Indicates PSU redundancy
* - Part Number: The part number of the PSU unit
* - Model: The model identifier for the PSU
* - Manufacturer: The manufacturer of the PSU unit
*
* Creates a Custom Driver Variables
* - Power Usage: The current power consumption of the system
* - Power Capacity: The maximum power capacity available
* - Capacity Percentage: The percentage of power capacity currently being used
*/

// SSH command to retrieve PSU information
var command = "racadm hwinventory";
var psuCommand = 'racadm hwinventory'
var powerUsageCommand = 'racadm getsensorinfo'


// SSH options when running the command
var sshConfig = {
"username": D.device.username(),
"password": D.device.password(),
"timeout": 30000,
"keyboard_interactive": true
};
'username': D.device.username(),
'password': D.device.password(),
'timeout': 30000,
'keyboard_interactive': true,
'port': 52144
}

// Custom Driver Table to store PSU information
var table = D.createTable(
"PSU Info",
'PSU Info',
[
{ label: "Description", valueType: D.valueType.STRING },
{ label: "Primary Status", valueType: D.valueType.STRING },
{ label: "Total Output Power", unit: "W", valueType: D.valueType.NUMBER },
{ label: "Input Voltage", unit: "V", valueType: D.valueType.NUMBER },
{ label: "Redundancy Status", valueType: D.valueType.STRING },
{ label: "Part Number", valueType: D.valueType.STRING },
{ label: "Model", valueType: D.valueType.STRING },
{ label: "Manufacturer", valueType: D.valueType.STRING }
{ label: 'Description', valueType: D.valueType.STRING },
{ label: 'Primary Status', valueType: D.valueType.STRING },
{ label: 'Total Output Power', unit: 'W', valueType: D.valueType.NUMBER },
{ label: 'Input Voltage', unit: 'V', valueType: D.valueType.NUMBER },
{ label: 'Redundancy Status', valueType: D.valueType.STRING },
{ label: 'Part Number', valueType: D.valueType.STRING },
{ label: 'Model', valueType: D.valueType.STRING },
{ label: 'Manufacturer', valueType: D.valueType.STRING }
]
);
)

//Handle SSH errors
/**
* Handles SSH errors and logs them
* @param {Object} err The error object from the SSH command
*/
function checkSshError(err) {
if(err.message) console.error(err.message);
if(err.code == 5) D.failure(D.errorType.AUTHENTICATION_ERROR);
if(err.code == 255) D.failure(D.errorType.RESOURCE_UNAVAILABLE);
console.error(err);
D.failure(D.errorType.GENERIC_ERROR);
if(err.message) console.error(err.message)
if(err.code == 5) D.failure(D.errorType.AUTHENTICATION_ERROR)
if(err.code == 255) D.failure(D.errorType.RESOURCE_UNAVAILABLE)
console.error(err)
D.failure(D.errorType.GENERIC_ERROR)
}

// Execute SSH command and return a promise
/**
* Executes an SSH command on the Dell iDRAC device
* @param {string} command The command to execute on iDRAC
* @returns {Promise} Resolves with the command output, or rejects with an error
*/
function executeCommand(command) {
var d = D.q.defer();
sshConfig.command = command;
var d = D.q.defer()
sshConfig.command = command
D.device.sendSSHCommand(sshConfig, function (output, err) {
if (err) {
checkSshError(err);
checkSshError(err)
} else {
if (output && output.indexOf("COMMAND NOT RECOGNIZED")!==-1) {
D.failure(D.errorType.PARSING_ERROR);
if (output && output.indexOf('COMMAND NOT RECOGNIZED')!==-1 || output.indexOf('ERROR')!==-1) {
D.failure(D.errorType.PARSING_ERROR)
} else {
d.resolve(output);
d.resolve(output)
}
}
});
return d.promise;
})
return d.promise
}

/**
* Executes both the PSU status command and power usage command
* @returns {Promise} Resolves with the combined output of PSU and power usage commands
*/
function executeDelliDracCommands(){
return executeCommand(psuCommand)
.then(function (psuOutput) {
return executeCommand(powerUsageCommand)
.then(function (powerUsageOutput) {
return { psuOutput, powerUsageOutput }
})
})
}

/**
* Sanitizes the output by removing reserved words and formatting it
* @param {string} output The string to be sanitized
* @returns {string} The sanitized string
*/
function sanitize(output) {
const recordIdReservedWords = ['\\?', '\\*', '\\%', 'table', 'column', 'history']
const recordIdSanitizationRegex = new RegExp(recordIdReservedWords.join('|'), 'g')
return output.replace(recordIdSanitizationRegex, '').slice(0, 50).replace(/\s+/g, '-').toLowerCase()
}

/**
* Inserts a new PSU record into the table.
* @param {Object} recordData - The PSU record data to insert.
*/
function insertPsuRecord(recordData) {
table.insertRecord(
recordData.recordId, [
recordData.description,
recordData.primaryStatus,
recordData.totalOutputPower,
recordData.inputVoltage,
recordData.redundancyStatus,
recordData.partNumber,
recordData.model,
recordData.manufacturer
]
)
}

function extractData(output) {
if (output) {
var psulines = output.psuOutput.split('\n');
var data = {};
var instanceIdPsu = false;
for (var i = 0; i < psulines.length; i++) {
var line = psulines[i].trim();
if (line.indexOf('[InstanceID: PSU.Slot.') >= 0) {
instanceIdPsu = true;
data = {};
} else if (instanceIdPsu && line.length === 0) {
var recordData = {
recordId: sanitize(data['InstanceID']),
description: data['DeviceDescription'] || 'N/A',
primaryStatus: data['PrimaryStatus'] || 'N/A',
totalOutputPower: (data['TotalOutputPower'] || '').replace(/\D+/g, '') || 'N/A',
inputVoltage: (data['InputVoltage'] || '').replace(/\D+/g, '') || 'N/A',
redundancyStatus: data['RedundancyStatus'] || 'N/A',
partNumber: data['PartNumber'] || 'N/A',
model: data['Model'] || 'N/A',
manufacturer: data['Manufacturer'] || 'N/A'
};
insertPsuRecord(recordData)
data = {}
instanceIdPsu = false
} else if (instanceIdPsu) {
var keyValue = line.split('=')
if (keyValue.length === 2) {
var key = keyValue[0].trim()
var value = keyValue[1].trim()
data[key] = value
}
}
}
var powerUsagelines = output.powerUsageOutput.split('\n');
for (var i = 0; i < powerUsagelines.length; i++) {
var line = powerUsagelines[i].trim()
if (line.indexOf('System Board Pwr Consumption') !== -1) {
var parts = line.split(/\s+/)
var powerUsage = parseInt(parts[5].replace('Watts', '').trim()) || 'N/A';
var powerCapacity = parseInt(parts[7].replace('Watts', '').trim()) || 'N/A';
if (powerUsage && powerCapacity) {
var capacityPercentage = ((powerUsage / powerCapacity) * 100).toFixed(2);
}
}
}
var variables = [
D.createVariable("power-usage", "Power Usage", powerUsage, 'W', D.valueType.NUMBER),
D.createVariable("capacity", "Maximum Capacity Usage", powerCapacity, 'W', D.valueType.NUMBER),
D.createVariable("capacity-percentage", "Capacity Percentage", capacityPercentage, '%', D.valueType.NUMBER)
]
D.success(variables, table)
} else {
console.error("No output provided.")
D.failure(D.errorType.GENERIC_ERROR)
}
}

/**
Expand All @@ -78,73 +195,28 @@ function executeCommand(command) {
* @documentation This procedure validates if the driver can be applied to a device during association and validates provided credentials.
*/
function validate() {
executeCommand(command)
executeDelliDracCommands()
.then(parseValidateOutput)
.then(D.success)
.catch(checkSshError);
.catch(checkSshError)
}

function parseValidateOutput(output) {
if (output.trim() !== "") {
console.info("Validation successful");
}
if (!output || !output.psuOutput|| !output.powerUsageOutput) {
console.error("Validation failed: One or both of the command outputs are invalid.")
D.failure(D.errorType.GENERIC_ERROR)
} else {
console.log("Validation successful")
}
}

/**
* @remote_procedure
* @label Get PSU Info
* @documentation Retrieves operational status of Power Supply Units (PSUs) on a Dell server with iDRAC.
* @documentation Retrieves operational status of Power Supply Units (PSUs) on a Dell server with iDRAC
*/
function get_status() {
executeCommand(command)
.then(parseData)
.catch(checkSshError);
executeDelliDracCommands()
.then(extractData)
.catch(checkSshError)
}

// Parse cpu information output
function parseData(output) {
var lines = output.split("\n");
var data = {};
var instanceIdPsu = false;
var recordIdReservedWords = ['\\?', '\\*', '\\%', 'table', 'column', 'history'];
var recordIdSanitisationRegex = new RegExp(recordIdReservedWords.join('|'), 'g');
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (line.indexOf("[InstanceID: PSU.Slot.") >= 0) {
instanceIdPsu = true;
data = {};
} else if (instanceIdPsu && line.length === 0) {
var recordId = (data["InstanceID"]).replace(recordIdSanitisationRegex, '').slice(0, 50).replace(/\s+/g, '-').toLowerCase();
var description = data["DeviceDescription"] || "-";
var primaryStatus = data["PrimaryStatus"] || "-";
var totalOutputPower = (data["TotalOutputPower"] || "").replace(/\D+/g, "") || "-";
var inputVoltage = (data["InputVoltage"] || "").replace(/\D+/g, "") || "-";
var redundancyStatus = data["RedundancyStatus"] || "-";
var partNumber = data["PartNumber"] || "-";
var model = data["Model"] || "-";
var manufacturer = data["Manufacturer"] || "-";
table.insertRecord(
recordId, [
description,
primaryStatus,
totalOutputPower,
inputVoltage,
redundancyStatus,
partNumber,
model,
manufacturer
]
);
data = {};
instanceIdPsu = false;
} else if (instanceIdPsu) {
var keyValue = line.split('=');
if (keyValue.length === 2) {
var key = keyValue[0].trim();
var value = keyValue[1].trim();
data[key] = value;
}
}
}
D.success(table);
}