Project

General

Profile

Bug #52372 ยป capacity_check.php

Kevin Meijer, 08/23/2021 11:44 AM

 
#!/usr/bin/env php
<?php

declare(strict_types = 1);

/*
* Functions section
*/

$apiToken = NULL;
$silent = !in_array('-v', $argv);
$newToken = in_array('-t', $argv);

function readableSize(float $in) : string {
// Bytes to Tebibyte, rounded to 2 digits
return (string) round($in / 1.1e+12, 2) . 'TB';
}

function logStr(string $message, bool $force = FALSE) : void {

global $silent;

if (!$silent || $force) {
echo date('H:i:s') . ' ' . $message . PHP_EOL;
}

}

function exitWithStatus(string $severity, string $message) : void {

$exitCodeMap = [
'OK' => 0,
'WARNING' => 1,
'CRITICAL' => 2,
'UNKNOWN' => 3,
];

$exitCode = $exitCodeMap[$severity] ?? NULL;
if ($exitCode === NULL) {
exitWithStatus('UNKNOWN', 'Unknown severity "' . $severity . '"');
}

echo($severity . ' - ' . $message . PHP_EOL);

exit($exitCode);
}

function createBasicCURLHandle(string $url, array $additionalHeaders = []) {

global $apiEndpoint, $apiPort, $apiUsername, $apiPassword, $apiToken, $apiVerifySSL, $apiTokenCachePath;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://' . $apiEndpoint . $url);
curl_setopt($ch, CURLOPT_PORT, $apiPort);
curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(['Content-Type: application/json', 'Accept: application/vnd.ceph.api.v1.0+json'], $additionalHeaders));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

if (!$apiVerifySSL) {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
}

return $ch;
}

function checkCurlResponse($ch) : void {

$info = curl_getinfo($ch);

$apiStr = parse_url($info['url'], PHP_URL_PATH);

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($httpCode === 0) {
curl_close($ch);
exitWithStatus('UNKNOWN', 'Got CURL error for endpoint "' . $apiStr . '": ' . curl_error($ch));
} elseif ($httpCode < 200 || $httpCode >= 300) {
curl_close($ch);
exitWithStatus('UNKNOWN', 'Unexpected HTTP code from API endpoint "' . $apiStr . '": ' . $httpCode);
}

}

function getAPIToken() : string {

global $apiEndpoint, $apiUsername, $apiPassword, $apiToken, $apiTokenCachePath;

if ($apiToken === NULL) {

if (file_exists($apiTokenCachePath)) {

$tokenDataStr = file_get_contents($apiTokenCachePath);
$tokenData = json_decode($tokenDataStr);

// The token needs to be valid at least for 10 more seconds
if ($tokenData->validUntil > time() + 10) {
$apiToken = $tokenData->token;
}

}

}

if ($apiToken === NULL) {

logStr('Retrieving new API token');

$ch = createBasicCURLHandle('/api/auth');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'username' => $apiUsername,
'password' => $apiPassword,
]));

$jsonStr = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

checkCurlResponse($ch);
curl_close($ch);

$payload = json_decode($jsonStr);

$apiToken = $payload->token;

file_put_contents($apiTokenCachePath, json_encode([
'validUntil' => time() + (60 * 60 * 8), // Todo: Read from JWT instead, here we assume 8 hours
'token' => $apiToken,
]));
chmod($apiTokenCachePath, 0600);

}

return $apiToken;
}

function doGet(string $url) {

global $apiEndpoint;

$ch = createBasicCURLHandle($url, ['Authorization: Bearer ' . getAPIToken()]);

$jsonStr = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

checkCurlResponse($ch);
curl_close($ch);

return json_decode($jsonStr);
}

/*
* End functions section
*
* Loading config
*/

$configFile = '/etc/nrpe-ceph.json';

if (!file_exists($configFile)) {
exitWithStatus('UNKNOWN', 'Config file "' . $configFile . '" not found');
}

$configData = json_decode(file_get_contents($configFile), TRUE);

$warningPercentageOffset = (int) ($configData['warningPercentageOffset'] ?? 5);
$allowedNodeFailures = (int) ($configData['allowedNodeFailures'] ?? 1);

$apiEndpoint = $configData['api']['host'];
$apiPort = $configData['api']['port'] ?? 8443;
$apiUsername = $configData['api']['username'];
$apiPassword = $configData['api']['password'];
$apiVerifySSL = $configData['api']['verifySSL'] ?? TRUE;
$apiTokenCachePath = $configData['api']['tokenCachePath'] ?? '/tmp/nrpe-ceph-token';

/*
* End of config loading
*/

if ($newToken && file_exists($apiTokenCachePath)) {

logStr('Removing old cached');
unlink($apiTokenCachePath);

}

logStr('Loading data..');

$osds = doGet('/api/osd');
$clusterConfig = doGet('/api/cluster_conf');

$mon_osd_nearfull_ratio = NULL;

logStr('Done');
logStr('Parsing..');

foreach ($clusterConfig as $configKey) {

if ($configKey->name === 'mon_osd_nearfull_ratio') {
$mon_osd_nearfull_ratio = $configKey->default; // Todo: Get the actual value
}

}

if ($mon_osd_nearfull_ratio === NULL) {
exitWithStatus('UNKNOWN', 'Unable to find config key "mon_osd_nearfull_ratio"');
}

$basePercentageEmpty = (100 - ($mon_osd_nearfull_ratio * 100));

$clusterCapacity = 0;
$clusterUsed = 0;
$nodeInfo = [];

foreach ($osds as $osd) {

$id = $osd->osd;
$stats = $osd->osd_stats;

$sizeInBytes = $stats->kb * 1024;
$usedSizeInBytes = $stats->kb_used * 1024;

$hostId = $osd->host->id;
$hostName = $osd->host->name;

if (!isset($nodeInfo[$hostId])) {
$nodeInfo[$hostId] = [
'name' => $hostName,
'capacity' => 0,
];
}

$nodeInfo[$hostId]['capacity'] += $sizeInBytes;

$clusterCapacity += $sizeInBytes;
$clusterUsed += $usedSizeInBytes;

}

$emptySpaceRequiredPercentage = $basePercentageEmpty;

usort($nodeInfo, function(array $infoA, $infoB) {
return $infoB['capacity'] <=> $infoA['capacity'];
});

foreach ($nodeInfo as $node) {

$nodePercentage = (100 * $node['capacity']) / $clusterCapacity;
logStr($node['name'] . ': ' . readableSize($node['capacity']) . ' (' . round($nodePercentage, 2) . '%)');

}

$i = 0;
foreach ($nodeInfo as $node) {

if ($i >= $allowedNodeFailures) {
break;
}

$nodePercentage = (100 * $node['capacity']) / $clusterCapacity;

$emptySpaceRequiredPercentage += $nodePercentage;

$i++;

}

$maxUsagePercentage = 100 - $emptySpaceRequiredPercentage;
$usagePercentage = (100 / $clusterCapacity) * $clusterUsed;

$additionalInfo = '';
$severity = 'OK';

if ($usagePercentage > $maxUsagePercentage) {

$severity = 'CRITICAL';

$percentageRequired = ($usagePercentage - $maxUsagePercentage);
$bytesRequired = ($clusterCapacity / 100) * $percentageRequired;

$additionalInfo = ', at least an additional ' . readableSize($bytesRequired) . ' of capacity is required';

} elseif (($usagePercentage + $warningPercentageOffset) > $maxUsagePercentage) {
$severity = 'WARNING';
}

exitWithStatus($severity, round($usagePercentage, 2) . '% used out of allowed ' . round($maxUsagePercentage, 2) . '%' . $additionalInfo);
    (1-1/1)