Add basic tests for node-init utilities

This commit is contained in:
Ashton Kinslow 2025-06-12 23:05:28 -05:00
parent 8193b689ed
commit 2b93cc4457
10 changed files with 240 additions and 2 deletions

View File

@ -68,7 +68,13 @@ Predominantly 3 things are needed:
## Node Prep ## Node Prep
You should install/configure the requirements for both nfs and iscsi. You should install/configure the requirements for both nfs and iscsi. A helper
command `node-init` is provided to automate this process. Invoke it with one or
more driver names and it will run the appropriate initialization steps:
```
node-init --driver iscsi --driver nfs
```
### cifs ### cifs

18
bin/node-init Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env node
const { run } = require('../src/node-init');
const args = require('yargs')
.scriptName('node-init')
.usage('$0 --driver <name> [--driver <name> ...]')
.option('driver', {
alias: 'd',
type: 'array',
demandOption: true,
describe: 'CSI driver name to initialize',
})
.help().argv;
run(args.driver).catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -3,8 +3,12 @@
"version": "1.9.0", "version": "1.9.0",
"description": "kubernetes csi driver framework", "description": "kubernetes csi driver framework",
"main": "bin/democratic-csi", "main": "bin/democratic-csi",
"bin": {
"democratic-csi": "bin/democratic-csi",
"node-init": "bin/node-init"
},
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "node --test",
"start": "./bin/democratic-csi" "start": "./bin/democratic-csi"
}, },
"author": "Travis Glenn Hansen <travisghansen@yahoo.com>", "author": "Travis Glenn Hansen <travisghansen@yahoo.com>",

View File

@ -0,0 +1,7 @@
const { installPackages } = require('../utils');
async function run() {
installPackages({ apt: ['cifs-utils'], yum: ['cifs-utils'] });
}
module.exports = { run };

View File

@ -0,0 +1,26 @@
const { installPackages, enableService, startService, runCommand, commandExists } = require('../utils');
const fs = require('fs');
async function run() {
installPackages({
apt: ['open-iscsi','lsscsi','sg3-utils','multipath-tools','scsitools'],
yum: ['lsscsi','iscsi-initiator-utils','sg3_utils','device-mapper-multipath']
});
if (!fs.existsSync('/etc/multipath.conf')) {
// configuration may be needed but is skipped if file is absent
}
if (commandExists('mpathconf')) {
try {
runCommand('mpathconf', ['--enable','--with_multipathd','y']);
} catch (e) {}
}
;['iscsid','multipathd','iscsi','open-iscsi'].forEach(s => {
enableService(s);
startService(s);
});
}
module.exports = { run };

View File

@ -0,0 +1,7 @@
const { installPackages } = require('../utils');
async function run() {
installPackages({ apt: ['nfs-common'], yum: ['nfs-utils'] });
}
module.exports = { run };

View File

@ -0,0 +1,15 @@
const { installPackages, loadModule } = require('../utils');
const fs = require('fs');
async function run() {
installPackages({ apt: ['nvme-cli','linux-generic'], yum: ['nvme-cli'] });
const config = '/etc/modules-load.d/nvme.conf';
if (!fs.existsSync(config)) {
fs.writeFileSync(config, 'nvme\nnvme-tcp\nnvme-fc\nnvme-rdma\n');
}
['nvme','nvme-tcp','nvme-fc','nvme-rdma'].forEach(loadModule);
}
module.exports = { run };

22
src/node-init/index.js Normal file
View File

@ -0,0 +1,22 @@
const fs = require('fs');
const path = require('path');
async function run(drivers) {
for (const name of drivers) {
const file = path.join(__dirname, 'drivers', `${name}.js`);
if (!fs.existsSync(file)) {
console.log(`no node init script for driver: ${name}`);
continue;
}
const mod = require(file);
try {
if (typeof mod.run === 'function') {
await mod.run();
}
} catch (e) {
console.error(`driver ${name} failed: ${e.toString()}`);
}
}
}
module.exports = { run };

59
src/node-init/utils.js Normal file
View File

@ -0,0 +1,59 @@
const { spawnSync } = require('child_process');
const fs = require('fs');
function commandExists(cmd) {
const res = spawnSync('which', [cmd]);
return res.status === 0;
}
function runCommand(cmd, args) {
console.log(`executing: ${cmd} ${args.join(' ')}`);
const res = spawnSync(cmd, args, { stdio: 'inherit' });
if (res.error) throw res.error;
if (res.status !== 0) throw new Error(`${cmd} exited with code ${res.status}`);
}
function installPackages(pkgs) {
if (!pkgs) return;
if (commandExists('apt-get')) {
(pkgs.apt || []).forEach((p) => {
if (spawnSync('dpkg', ['-s', p], { stdio: 'ignore' }).status !== 0) {
runCommand('apt-get', ['install', '-y', p]);
}
});
} else if (commandExists('yum')) {
(pkgs.yum || []).forEach((p) => {
if (spawnSync('rpm', ['-q', p], { stdio: 'ignore' }).status !== 0) {
runCommand('yum', ['install', '-y', p]);
}
});
} else {
console.log('no supported package manager found');
}
}
function enableService(name) {
if (commandExists('systemctl')) {
if (spawnSync('systemctl', ['is-enabled', name], { stdio: 'ignore' }).status !== 0) {
runCommand('systemctl', ['enable', name]);
}
}
}
function startService(name) {
if (commandExists('systemctl')) {
if (spawnSync('systemctl', ['is-active', name], { stdio: 'ignore' }).status !== 0) {
runCommand('systemctl', ['start', name]);
}
}
}
function loadModule(name) {
if (!fs.existsSync(`/sys/module/${name}`)) {
if (commandExists('modprobe')) {
runCommand('modprobe', [name]);
}
}
}
module.exports = { commandExists, runCommand, installPackages, enableService, startService, loadModule };

View File

@ -0,0 +1,74 @@
const test = require('node:test');
const assert = require('node:assert');
function loadUtils(mockSpawn, mockExists) {
const cp = require('child_process');
const fs = require('fs');
cp.spawnSync = mockSpawn;
if (mockExists) fs.existsSync = mockExists;
delete require.cache[require.resolve('../src/node-init/utils')];
return require('../src/node-init/utils');
}
test('commandExists true', () => {
const calls = [];
const utils = loadUtils((...args) => { calls.push(args); return {status:0}; });
assert.strictEqual(utils.commandExists('ls'), true);
assert.deepStrictEqual(calls[0], ['which', ['ls']]);
});
test('commandExists false', () => {
const utils = loadUtils(() => ({status:1}));
assert.strictEqual(utils.commandExists('ls'), false);
});
test('runCommand success', () => {
const calls = [];
const utils = loadUtils((...args) => { calls.push(args); return {status:0}; });
assert.doesNotThrow(() => utils.runCommand('cmd', ['arg']));
assert.deepStrictEqual(calls[0], ['cmd', ['arg'], {stdio:'inherit'}]);
});
test('runCommand failure', () => {
const utils = loadUtils(() => ({status:1}));
assert.throws(() => utils.runCommand('cmd', []));
});
test('installPackages apt missing', () => {
const calls = [];
const responses = [
{status:0}, // which apt-get
{status:1}, // dpkg -s
{status:0}, // apt-get install
];
const mock = (...args) => { calls.push(args); return responses.shift(); };
const utils = loadUtils(mock);
utils.installPackages({apt:['pkg']});
assert.deepStrictEqual(calls[2], ['apt-get', ['install','-y','pkg'], {stdio:'inherit'}]);
});
test('installPackages apt installed', () => {
const calls = [];
const responses = [
{status:0}, // which apt-get
{status:0}, // dpkg -s (installed)
];
const mock = (...args) => { calls.push(args); return responses.shift(); };
const utils = loadUtils(mock);
utils.installPackages({apt:['pkg']});
assert.strictEqual(calls.length, 2); // no install call
});
test('installPackages yum missing', () => {
const calls = [];
const responses = [
{status:1}, // which apt-get
{status:0}, // which yum
{status:1}, // rpm -q
{status:0}, // yum install
];
const mock = (...args) => { calls.push(args); return responses.shift(); };
const utils = loadUtils(mock);
utils.installPackages({yum:['pkg']});
assert.deepStrictEqual(calls[3], ['yum', ['install','-y','pkg'], {stdio:'inherit'}]);
});