2019-08-19 13:50:44 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
2021-01-04 11:05:42 -08:00
|
|
|
getDisks,
|
|
|
|
|
checkDiskSpace,
|
2021-01-20 11:45:04 -08:00
|
|
|
getMemory,
|
|
|
|
|
getMemoryAllocation
|
2019-08-19 13:50:44 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const apps = require('./apps.js'),
|
|
|
|
|
assert = require('assert'),
|
|
|
|
|
async = require('async'),
|
2019-10-22 11:11:41 -07:00
|
|
|
BoxError = require('./boxerror.js'),
|
2019-08-19 13:50:44 -07:00
|
|
|
debug = require('debug')('box:disks'),
|
|
|
|
|
df = require('@sindresorhus/df'),
|
|
|
|
|
docker = require('./docker.js'),
|
|
|
|
|
notifications = require('./notifications.js'),
|
2019-11-21 12:55:17 -08:00
|
|
|
os = require('os'),
|
|
|
|
|
paths = require('./paths.js'),
|
2020-01-31 13:37:07 -08:00
|
|
|
safe = require('safetydance'),
|
2021-01-04 11:05:42 -08:00
|
|
|
settings = require('./settings.js'),
|
|
|
|
|
volumes = require('./volumes.js');
|
2019-08-19 13:50:44 -07:00
|
|
|
|
2021-01-04 11:05:42 -08:00
|
|
|
const dfAsync = async.asyncify(df), dfFileAsync = async.asyncify(df.file);
|
|
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
async function getVolumeDisks(appsDataDisk) {
|
2021-01-27 21:47:36 -08:00
|
|
|
assert.strictEqual(typeof appsDataDisk, 'string');
|
2019-08-19 13:50:44 -07:00
|
|
|
|
2021-01-04 11:05:42 -08:00
|
|
|
let volumeDisks = {};
|
2021-05-11 17:50:48 -07:00
|
|
|
const allVolumes = await volumes.list();
|
2021-01-04 11:05:42 -08:00
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
for (const volume of allVolumes) {
|
|
|
|
|
const [error, result] = await safe(df(volume.hostPath));
|
|
|
|
|
volumeDisks[volume.id] = error ? appsDataDisk : result.filesystem; // ignore any errors
|
|
|
|
|
}
|
2021-01-04 11:05:42 -08:00
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
return volumeDisks;
|
2021-01-04 11:05:42 -08:00
|
|
|
}
|
|
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
async function getAppDisks(appsDataDisk) {
|
2021-01-27 21:47:36 -08:00
|
|
|
assert.strictEqual(typeof appsDataDisk, 'string');
|
2021-01-04 11:05:42 -08:00
|
|
|
|
|
|
|
|
let appDisks = {};
|
|
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
apps.getAll(async function (error, allApps) {
|
|
|
|
|
if (error) return reject(error);
|
|
|
|
|
|
|
|
|
|
for (const app of allApps) {
|
|
|
|
|
if (!app.dataDir) {
|
|
|
|
|
appDisks[app.id] = appsDataDisk;
|
|
|
|
|
} else {
|
|
|
|
|
const [error, result] = await safe(df.file(app.dataDir));
|
|
|
|
|
appDisks[app.id] = error ? appsDataDisk : result.filesystem; // ignore any errors
|
|
|
|
|
}
|
2021-01-04 11:05:42 -08:00
|
|
|
}
|
|
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
resolve(appDisks);
|
2021-01-04 11:05:42 -08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getBackupDisk(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
settings.getBackupConfig(function (error, backupConfig) {
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
if (backupConfig.provider !== 'filesystem') return callback(null, null);
|
|
|
|
|
|
|
|
|
|
dfFileAsync(backupConfig.backupFolder, function (error, result) {
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
callback(null, result.filesystem);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getDisks(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
2019-08-19 13:50:44 -07:00
|
|
|
|
|
|
|
|
docker.info(function (error, info) {
|
2019-10-22 11:11:41 -07:00
|
|
|
if (error) return callback(error);
|
2019-08-19 13:50:44 -07:00
|
|
|
|
|
|
|
|
async.series([
|
|
|
|
|
dfAsync,
|
|
|
|
|
dfFileAsync.bind(null, paths.BOX_DATA_DIR),
|
|
|
|
|
dfFileAsync.bind(null, paths.PLATFORM_DATA_DIR),
|
|
|
|
|
dfFileAsync.bind(null, paths.APPS_DATA_DIR),
|
2021-01-04 11:05:42 -08:00
|
|
|
dfFileAsync.bind(null, info.DockerRootDir),
|
|
|
|
|
getBackupDisk,
|
2021-05-11 17:50:48 -07:00
|
|
|
], async function (error, values) {
|
2019-10-22 11:11:41 -07:00
|
|
|
if (error) return callback(new BoxError(BoxError.FS_ERROR, error));
|
2019-08-19 13:50:44 -07:00
|
|
|
|
|
|
|
|
// filter by ext4 and then sort to make sure root disk is first
|
|
|
|
|
const ext4Disks = values[0].filter((r) => r.type === 'ext4').sort((a, b) => a.mountpoint.localeCompare(b.mountpoint));
|
|
|
|
|
|
|
|
|
|
const disks = {
|
2020-04-01 16:26:16 -07:00
|
|
|
disks: ext4Disks, // root disk is first. { filesystem, type, size, used, avialable, capacity, mountpoint }
|
2019-08-19 13:50:44 -07:00
|
|
|
boxDataDisk: values[1].filesystem,
|
|
|
|
|
mailDataDisk: values[1].filesystem,
|
|
|
|
|
platformDataDisk: values[2].filesystem,
|
|
|
|
|
appsDataDisk: values[3].filesystem,
|
|
|
|
|
dockerDataDisk: values[4].filesystem,
|
2021-01-04 11:05:42 -08:00
|
|
|
backupsDisk: values[5],
|
2021-01-27 21:47:36 -08:00
|
|
|
apps: {}, // filled below
|
|
|
|
|
volumes: {} // filled below
|
2019-08-19 13:50:44 -07:00
|
|
|
};
|
|
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
[error, disks.apps] = await safe(getAppDisks(disks.appsDataDisk));
|
|
|
|
|
if (error) return callback(error);
|
2021-01-27 21:47:36 -08:00
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
[error, disks.volumes] = await safe(getVolumeDisks(disks.appsDataDisk));
|
|
|
|
|
if (error) return callback(error);
|
2021-01-27 21:47:36 -08:00
|
|
|
|
2021-05-11 17:50:48 -07:00
|
|
|
callback(null, disks);
|
2019-08-19 13:50:44 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkDiskSpace(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
debug('Checking disk space');
|
|
|
|
|
|
|
|
|
|
getDisks(function (error, disks) {
|
|
|
|
|
if (error) {
|
|
|
|
|
debug('checkDiskSpace: error getting disks %s', error.message);
|
|
|
|
|
return callback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var oos = disks.disks.some(function (entry) {
|
|
|
|
|
// ignore other filesystems but where box, app and platform data is
|
|
|
|
|
if (entry.filesystem !== disks.boxDataDisk
|
|
|
|
|
&& entry.filesystem !== disks.platformDataDisk
|
|
|
|
|
&& entry.filesystem !== disks.appsDataDisk
|
2020-01-31 22:20:34 -08:00
|
|
|
&& entry.filesystem !== disks.backupsDisk
|
2019-08-19 13:50:44 -07:00
|
|
|
&& entry.filesystem !== disks.dockerDataDisk) return false;
|
|
|
|
|
|
|
|
|
|
return (entry.available <= (1.25 * 1024 * 1024 * 1024)); // 1.5G
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
debug('checkDiskSpace: disk space checked. ok: %s', !oos);
|
|
|
|
|
|
|
|
|
|
notifications.alert(notifications.ALERT_DISK_SPACE, 'Server is running out of disk space', oos ? JSON.stringify(disks.disks, null, 4) : '', callback);
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-11-21 12:55:17 -08:00
|
|
|
|
2021-01-20 11:45:04 -08:00
|
|
|
function getSwapSize() {
|
2019-11-21 12:55:17 -08:00
|
|
|
const stdout = safe.child_process.execSync('swapon --noheadings --raw --bytes --show=SIZE', { encoding: 'utf8' });
|
2019-11-21 13:48:17 -08:00
|
|
|
const swap = !stdout ? 0 : stdout.trim().split('\n').map(x => parseInt(x, 10) || 0).reduce((acc, cur) => acc + cur);
|
2019-11-21 12:55:17 -08:00
|
|
|
|
2021-01-20 11:45:04 -08:00
|
|
|
return swap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getMemory(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2019-11-21 12:55:17 -08:00
|
|
|
callback(null, {
|
|
|
|
|
memory: os.totalmem(),
|
2021-01-20 11:45:04 -08:00
|
|
|
swap: getSwapSize()
|
2019-11-21 12:55:17 -08:00
|
|
|
});
|
|
|
|
|
}
|
2021-01-20 11:45:04 -08:00
|
|
|
|
|
|
|
|
function getMemoryAllocation(limit) {
|
2021-01-20 20:05:39 -08:00
|
|
|
let ratio = parseFloat(safe.fs.readFileSync(paths.SWAP_RATIO_FILE, 'utf8'), 10);
|
|
|
|
|
|
|
|
|
|
if (!ratio) {
|
|
|
|
|
const pc = os.totalmem() / (os.totalmem() + getSwapSize());
|
|
|
|
|
ratio = Math.round(pc * 10) / 10; // a simple ratio
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-20 11:45:04 -08:00
|
|
|
return Math.round(Math.round(limit * ratio) / 1048576) * 1048576; // nearest MB
|
|
|
|
|
}
|