WIP, simplify architecture for now

feature/node-rewrite
Sven Slootweg 1 year ago
parent 7bc156469f
commit 71d48f06cd

@ -0,0 +1,71 @@
"use strict";
const Promise = require("bluebird");
const memoizee = require("memoizee");
const DataLoader = require("dataloader");
const mapObj = require("map-obj");
const lvm = require("../../packages/exec-lvm");
const All = require("../../packages/graphql-interface/symbols/all");
// This generates a (memoized) source function for commands that always produce an entire list, that needs to be filtered for the desired item(s)
function makeListCommand({ command, selectResult, selectID }) {
let commandOnce = memoizee(command);
return function (ids) {
return Promise.try(() => {
return commandOnce();
}).then((result) => {
if (selectResult != null) {
return selectResult(result);
} else {
return result;
}
}).then((items) => {
return ids.map((id) => {
if (id === All) {
return items;
} else {
return items.find((item) => selectID(item, id));
}
});
});
};
}
module.exports = function createSources() {
let sources = {
lvmLogicalVolumes: makeListCommand({
command: lvm.getLogicalVolumes,
selectResult: (result) => result.volumes,
selectID: (volume, path) => (volume.path === path)
}),
lvmPhysicalVolumes: makeListCommand({
command: lvm.getPhysicalVolumes,
selectResult: (result) => result.volumes,
selectID: (device, path) => (device.path === path)
}),
lvmVolumeGroups: makeListCommand({
command: lvm.getVolumeGroups,
selectResult: (result) => result.groups,
selectID: (group, name) => (group.name === name)
}),
};
let factoryModules = {
lsblk: require("./lsblk")(),
findmnt: require("./findmnt")(),
smartctlInfo: require("./smartctl/info")(),
smartctlScan: require("./smartctl/scan")(),
smartctlAttributes: require("./smartctl/attributes")(),
nvmeListNamespaces: require("./nvme/list-namespaces")(),
nvmeIdentifyController: require("./nvme/identify-controller")(),
};
return mapObj({ ... sources, ... factoryModules }, (name, factory) => {
return [
name,
new DataLoader(factory)
];
});
};

@ -3,7 +3,7 @@
const Promise = require("bluebird");
const dlayer = require("../packages/dlayer");
const All = require("../packages/graphql-interface/symbols/all");
const loaders = require("./loaders");
const loaders = require("./data-sources");
const types = require("./types");
function typeFromSource(source, ids, factoryFunction) {

@ -1,25 +0,0 @@
"use strict";
const DataLoader = require("dataloader");
const mapObj = require("map-obj");
let dataSourceFactories = {
lsblk: require("./data-sources/lsblk"),
findmnt: require("./data-sources/findmnt"),
smartctlInfo: require("./data-sources/smartctl/info"),
smartctlScan: require("./data-sources/smartctl/scan"),
smartctlAttributes: require("./data-sources/smartctl/attributes"),
lvmPhysicalVolumes: require("./data-sources/lvm/physical-volumes"),
lvmVolumeGroups: require("./data-sources/lvm/volume-groups"),
nvmeListNamespaces: require("./data-sources/nvme/list-namespaces"),
nvmeIdentifyController: require("./data-sources/nvme/identify-controller"),
};
module.exports = function createLoaders() {
return mapObj(dataSourceFactories, (name, factory) => {
return [
name,
new DataLoader(factory())
];
});
};

@ -2,7 +2,7 @@
const Promise = require("bluebird");
const api = require("./");
const loaders = require("./loaders");
const loaders = require("./data-sources");
return Promise.try(() => {
return api.query({

@ -6,4 +6,5 @@ Object.assign(module.exports, {
Mount: require("./mount"),
LVMPhysicalVolume: require("./lvm/physical-volume"),
LVMVolumeGroup: require("./lvm/volume-group"),
LVMLogicalVolume: require("./lvm/logical-volume"),
});

@ -0,0 +1,58 @@
"use strict";
const dlayerSource = require("../../../packages/dlayer-source");
const types = require("..");
module.exports = function LVMLogicalVolume ({ path }) {
return dlayerSource.withSources({
$sources: {
lvmLogicalVolumes: {
[dlayerSource.ID]: path,
path: "path",
name: "name",
fullName: "fullName",
size: "size",
uuid: "uuid",
deviceMapperPath: "deviceMapperPath",
layoutAttributes: "layoutAttributes",
roles: "roles",
tags: "tags",
configurationProfile: "configurationProfile",
creationTime: "creationTime",
creationHost: "creationHost",
neededKernelModules: "neededKernelModules",
dataVolume: "dataVolume", // FIXME: Reference?
metadataVolume: "metadataVolume", // FIXME: Reference?
poolVolume: "poolVolume", // FIXME: Reference?
persistentMajorNumber: "persistentMajorNumber",
persistentMinorNumber: "persistentMinorNumber",
type: "type",
isReadOnly: "isReadOnly",
isCurrentlyReadOnly: "isCurrentlyReadOnly",
isAllocationLocked: "isAllocationLocked",
allocationPolicy: "allocationPolicy",
status: "status",
healthStatus: "healthStatus",
isInitiallySynchronized: "isInitiallySynchronized",
isCurrentlySynchronized: "isCurrentlySynchronized",
isMerging: "isMerging",
isConverting: "isConverting",
isSuspended: "isSuspended",
isActivationSkipped: "isActivationSkipped",
isOpened: "isOpened",
isActiveLocally: "isActiveLocally",
isActiveRemotely: "isActiveRemotely",
isActiveExclusively: "isActiveExclusively",
isMergeFailed: "isMergeFailed",
isSnapshotInvalid: "isSnapshotInvalid",
isLiveTablePresent: "isLiveTablePresent",
isInactiveTablePresent: "isInactiveTablePresent",
isZeroFilled: "isZeroFilled",
hasFixedMinorNumber: "hasFixedMinorNumber",
outOfSpacePolicy: "outOfSpacePolicy",
volumeGroup: (volume) => types.LVMVolumeGroup({ name: volume.volumeGroup })
}
}
});
};

@ -41,7 +41,6 @@ module.exports = function LVMVolumeGroup ({ name }) {
isIncomplete: "isIncomplete",
allocationPolicy: "allocationPolicy",
mode: "mode"
// FIXME: physicalVolumes, logicalVolumes
}
}
});

@ -80,9 +80,6 @@ module.exports = function () {
app.use(require("./routes/index"));
app.use("/disk-images", require("./routes/disk-images")(state));
app.use("/instances", require("./routes/instances")(state));
app.use("/hardware/storage-devices", require("./routes/storage-devices")(state));
app.use("/resource-pools", require("./routes/resource-pools")(state));
app.get("/hardware", (req, res) => {
// FIXME: default to /hardware/system-information instead, when that is implemented

@ -101,6 +101,8 @@ function assignErrorPath(error, cursor) {
}
}
// MARKER: build a sample todo-list schema for testing out fetches, mutations, and combinations of them, including on collections
function evaluate(cursor, context) {
// map query object -> result object
return asyncMapObject(cursor.query, (queryKey, subquery) => {

@ -3,7 +3,7 @@
const Promise = require("bluebird");
const dlayer = require("./");
const loaders = require("../../api/loaders");
const loaders = require("../../api/data-sources");
let schema = {
hardware: {

@ -21,7 +21,7 @@ module.exports = function () {
return execBinary("lvs")
.asRoot()
.withFlags({
options: "lv_all"
options: "lv_all,vg_name"
})
.withModifier(asJson((result) => {
return {
@ -29,6 +29,8 @@ module.exports = function () {
return {
path: volume.lv_path,
name: volume.lv_name, // NOTE: Not unique!
size: parseIECBytes(volume.lv_size), // NOTE: no totalSpace/freeSpace because that is handled by the filesystem
volumeGroup: volume.vg_name,
fullName: volume.lv_full_name,
uuid: volume.lv_uuid,
deviceMapperPath: volume.lv_dm_path,

@ -6,4 +6,20 @@ router.get("/", (req, res) => {
res.render("index");
});
router.get("/hardware/storage-devices", (req, res) => {
res.render("hardware/storage-devices/list");
});
router.get("/hardware/lvm", (req, res) => {
res.render("hardware/lvm/list");
});
router.get("/resource-pools/storage", (req, res) => {
res.render("resource-pools/storage/list");
});
router.get("/instances", (req, res) => {
res.render("instances/list");
});
module.exports = router;

@ -1,27 +0,0 @@
'use strict';
const Promise = require("bluebird");
module.exports = function({db}) {
let router = require("express-promise-router")();
router.get("/", (req, res) => {
return Promise.try(() => {
return db("instances");
}).then((instances) => {
res.render("instances/list", {
instances: instances
});
});
});
router.get("/add", (req, res) => {
res.render("instances/add");
});
router.post("/add", (req, res) => {
})
return router;
}

@ -1,13 +0,0 @@
'use strict';
const Promise = require("bluebird");
module.exports = function({db}) {
let router = require("express-promise-router")();
router.get("/storage", (req, res) => {
res.render("resource-pools/storage/list");
});
return router;
};

@ -1,80 +0,0 @@
'use strict';
const Promise = require("bluebird");
// const lsblk = require("../packages/exec-lsblk");
// const smartctl = require("../packages/exec-smartctl");
// const lvm = require("../packages/exec-lvm");
// const {B} = require("../units/bytes/iec");
// function getStorageDevices() {
// return Promise.try(() => {
// return lsblk();
// }).filter((device) => {
// /* FIXME: Move device type filter to GraphQL? */
// return (device.type === "disk");
// }).map((device) => {
// return Object.assign({}, device, {
// path: `/dev/${device.name}`
// });
// }).map((device) => {
// /* FIXME: Check whether we need to iterate through child disks as well, when dealing with eg. RAID arrays */
// return Promise.try(() => {
// return Promise.all([
// smartctl.info({ devicePath: device.path }),
// smartctl.attributes({ devicePath: device.path })
// ]);
// }).then(([info, attributes]) => {
// return Object.assign({}, device, {
// information: info,
// smartData: attributes,
// smartStatus: getSmartStatus(attributes)
// });
// });
// }).then((blockDevices) => {
// console.log(blockDevices);
// return blockDevices;
// });
// }
// function sumDriveSizes(drives) {
// return drives.reduce((total, device) => {
// return total + device.size.toB().amount;
// }, 0);
// }
// function roundUnit(unit) {
// return Object.assign(unit, {
// amount: Math.round(unit.amount * 100) / 100
// });
// }
module.exports = function({db}) {
let router = require("express-promise-router")();
router.get("/", (req, res) => {
// return Promise.try(() => {
// return getStorageDevices();
// }).then((devices) => {
// /* FIXME: Auto-formatting of total sizes and units */
// let fixedDrives = devices.filter((drive) => drive.removable === false);
// let removableDrives = devices.filter((drive) => drive.removable === true);
// let healthyFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "healthy");
// let deterioratingFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "deteriorating");
// let failingFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "failing");
// res.render("hardware/storage-devices/list", {
// devices: devices,
// totalFixedStorage: roundUnit(B(sumDriveSizes(fixedDrives)).toTiB()),
// totalHealthyFixedStorage: roundUnit(B(sumDriveSizes(healthyFixedDrives)).toTiB()),
// totalDeterioratingFixedStorage: roundUnit(B(sumDriveSizes(deterioratingFixedDrives)).toTiB()),
// totalFailingFixedStorage: roundUnit(B(sumDriveSizes(failingFixedDrives)).toTiB()),
// totalRemovableStorage: roundUnit(B(sumDriveSizes(removableDrives)).toGiB())
// });
// });
res.render("hardware/storage-devices/list");
});
return router;
}

@ -0,0 +1,20 @@
"use strict";
const React = require("react");
const util = require("util");
const ansiHTML = require("ansi-html-community");
// const entities = require("entities");
function escape(input) {
// NOTE: This is not necessarily secure! This is just a stopgap solution (used in development-mode code only) to not break display of < and > characters until we can figure out why `entities` doesn't combine with `ansi-html-community`.
return input.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
module.exports = function DebugView({ value }) {
if (process.env.NODE_ENV !== "development") {
return "[Debug view is disabled outside of development mode. If you see this, please contact the administrator.]";
} else {
let inspected = util.inspect(value, { colors: true, depth: null });
return <pre className="debugPrint" dangerouslySetInnerHTML={{ __html: ansiHTML(escape(inspected)) }} />;
}
};

@ -1,6 +0,0 @@
extends layout
block content
h1 An error occurred.
h2= error.message
pre= error.stack

@ -0,0 +1,44 @@
"use strict";
const React = require("react");
const Layout = require("../layout.jsx");
const DebugView = require("../../components/debug-view.jsx");
module.exports = {
query: {
resources: {
lvm: {
volumeGroups: {
name: true,
totalSpace: true,
freeSpace: true,
physicalVolumes: {
path: true,
format: true,
totalSpace: true,
freeSpace: true,
status: true
},
logicalVolumes: {
name: true,
path: true,
size: true,
status: true,
type: true,
healthStatus: true,
isReadOnly: true,
creationTime: true
}
}
}
}
},
template: function ({ data }) {
return (
<Layout title="LVM">
<DebugView value={data} />
</Layout>
);
}
};

@ -7,7 +7,7 @@ const splitFilterN = require("split-filter-n");
const { B } = require("../../../packages/unit-bytes-iec");
const treecutter = require("../../../packages/treecutter");
const Layout = require("../layout");
const Layout = require("../layout.jsx");
function sum(values) {
return values.reduce((total, value) => total + value, 0);

@ -1,70 +0,0 @@
extends ../../layout
block content
h2 Fixed drives
//- FIXME: Partitions with mountpoints
table.drives
tr
th SMART
th Device
th Total size
th RPM
th Serial number
th Model
th Family
th Firmware version
for device in devices.filter((device) => device.removable === false)
tr(class=(device.children.length > 0 ? "hasPartitions" : null))
td(class=`smart ${device.smartStatus}`, rowspan=(1 + device.children.length))
td= device.name
td= device.size
td #{device.information.rpm} RPM
td= device.information.serialNumber
td= device.information.model
td= device.information.modelFamily
td= device.information.firmwareVersion
for partition, i in device.children
tr.partition(class=(i === device.children.length - 1) ? "last" : null)
td= partition.name
td= partition.size
td(colspan=5)
if partition.mountpoint != null
= partition.mountpoint
else
span.notMounted (not mounted)
//- tr.partition
//- td(colspan=8)= JSON.stringify(partition)
tr
th(colspan=2) Total
td= totalFixedStorage
td(colspan=5).hidden
tr.smartStatus
th(colspan=2).healthy Healthy
td= totalHealthyFixedStorage
td(colspan=5).hidden
tr.smartStatus
th(colspan=2).atRisk At-risk
td= totalDeterioratingFixedStorage
td(colspan=5).hidden
tr.smartStatus
th(colspan=2).failing Failing
td= totalFailingFixedStorage
td(colspan=5).hidden
h2 Removable drives
table
tr
th Path
th Total size
th Mounted at
for device in devices.filter((device) => device.type === "loopDevice")
tr
td= device.path
td= device.size
td= device.mountpoint
Loading…
Cancel
Save