Move LVM schema into dlayer module

feature/node-rewrite
Sven Slootweg 10 months ago
parent a768a3f246
commit 300c58533f

@ -58,7 +58,7 @@
"debounce": "^1.0.0",
"debug": "^4.1.1",
"default-value": "^1.0.0",
"dlayer": "^0.1.0",
"dlayer": "^0.1.2",
"dotty": "^0.1.0",
"end-of-stream": "^1.1.0",
"entities": "^2.0.0",

@ -1,7 +1,6 @@
"use strict";
const Promise = require("bluebird");
const memoizee = require("memoizee");
const DataLoader = require("dataloader");
const mapObj = require("map-obj");
@ -10,32 +9,7 @@ const All = require("../../packages/graphql-interface/symbols/all");
const nvmeCLI = require("../../packages/exec-nvme-cli");
const smartctl = require("../../packages/exec-smartctl");
const dlayerWrap = require("../../packages/dlayer-wrap");
// 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 {
// TODO: Can this be more performant? Currently it is a nested loop
return items.find((item) => selectID(item) === id);
}
});
});
};
}
const evaluateAndPick = require("../../packages/evaluate-and-pick");
function makeSingleCommand({ command, selectResult }) {
return function (ids) {
@ -58,28 +32,28 @@ function makeSingleCommand({ command, selectResult }) {
module.exports = function createSources() {
let sources = {
lvmLogicalVolumes: makeListCommand({
command: lvm.getLogicalVolumes,
selectResult: (result) => result.volumes,
selectID: (volume) => volume.path
}),
lvmPhysicalVolumes: makeListCommand({
command: lvm.getPhysicalVolumes,
selectResult: (result) => result.volumes,
selectID: (device) => device.path
}),
lvmVolumeGroups: makeListCommand({
command: lvm.getVolumeGroups,
selectResult: (result) => result.groups,
selectID: (group) => group.name
}),
// lvmLogicalVolumes: evaluateAndPick({
// command: lvm.getLogicalVolumes,
// selectResult: (result) => result.volumes,
// selectID: (volume) => volume.path
// }),
// lvmPhysicalVolumes: evaluateAndPick({
// command: lvm.getPhysicalVolumes,
// selectResult: (result) => result.volumes,
// selectID: (device) => device.path
// }),
// lvmVolumeGroups: evaluateAndPick({
// command: lvm.getVolumeGroups,
// selectResult: (result) => result.groups,
// selectID: (group) => group.name
// }),
nvmeIdentifyController: makeSingleCommand({
command: (path) => nvmeCLI.identifyController({ devicePath: path })
}),
nvmeListNamespaces: makeSingleCommand({
command: (path) => nvmeCLI.listNamespaces({ devicePath: path })
}),
smartctlScan: makeListCommand({
smartctlScan: evaluateAndPick({
command: smartctl.scan,
selectID: (device) => device.path
}),

@ -31,6 +31,9 @@ module.exports = function () {
sources: loaders()
};
},
modules: [
require("../packages/sysquery-lvm")
],
schema: {
hardware: {
drives: ({ paths }, { sources }) => {
@ -41,37 +44,6 @@ module.exports = function () {
blockDevices: ({ names }, { sources }) => {
return typeFromSource(sources.lsblk, names, (device) => types.BlockDevice({ name: device.name }));
},
lvm: {
physicalVolumes: ({ paths }, { sources }) => {
return typeFromSource(sources.lvmPhysicalVolumes, paths, (volume) => types.LVMPhysicalVolume({ path: volume.path }));
},
volumeGroups: ({ names }, { sources }) => {
return typeFromSource(sources.lvmVolumeGroups, names, (group) => types.LVMVolumeGroup({ name: group.name }));
},
logicalVolumes: ({ paths }, { sources }) => {
return typeFromSource(sources.lvmLogicalVolumes, paths, (volume) => types.LVMPhysicalVolume({ path: volume.path }));
},
createPhysicalVolume: (args, { sources }) => {
let { path, force } = validateValue(args, {
path: [ required, isString ],
force: [ defaultTo(false), isBoolean ]
});
return Promise.try(() => {
return execLVM.createPhysicalVolume({ devicePath: path, force: force });
}).then(() => {
return types.LVMPhysicalVolume({ path });
});
},
createVolumeGroup: ({ name, physicalVolumes }) => {
return Promise.try(() => {
return execLVM.createVolumeGroup({ name, physicalVolumes });
}).then(() => {
return types.LVMVolumeGroup({ name });
});
}
// createLogicalVolume: ,
},
images: {
installationMedia: [],
vmImages: []

@ -4,7 +4,4 @@ Object.assign(module.exports, {
Drive: require("./drive"),
BlockDevice: require("./block-device"),
Mount: require("./mount"),
LVMPhysicalVolume: require("./lvm/physical-volume"),
LVMVolumeGroup: require("./lvm/volume-group"),
LVMLogicalVolume: require("./lvm/logical-volume"),
});

@ -22,13 +22,13 @@ module.exports = {
let getter = function (_args, context) {
return Promise.try(() => {
if (properties[ID] != null) {
let dataSource = context.sources[source];
let dataSource = context[source];
if (dataSource != null) {
// console.log(`Calling source '${source}' with ID ${util.inspect(properties[ID])}`);
return Result.wrapAsync(() => dataSource.load(properties[ID]));
} else {
throw new Error(`Attempted to read from source '${source}', but no such source is registered`);
throw new Error(`Attempted to read from context property '${source}', but no such property exists`);
}
} else {
// FIXME: Better error message
@ -41,7 +41,7 @@ module.exports = {
// TODO: How to deal with null results? Allow them or not? Make it an option?
if (result.isOK && result.value() == null) {
// TODO: Change implementation to allow `Result.ok(null|undefined)` but not `null|undefined` directly?
throw new Error(`Null-ish result returned for ID ${util.inspect(properties[ID])} from source '${source}'; this is not allowed, and there is probably a bug in your code. Please file a ticket if you have a good usecase for null-ish results!`);
throw new Error(`Null-ish result returned for ID ${util.inspect(properties[ID])} from source at context property '${source}'; this is not allowed, and there is probably a bug in your code. Please file a ticket if you have a good usecase for null-ish results!`);
} else if (properties[AllowErrors] === true && typeof selector !== "string") {
// Custom selectors always receive the Result as-is
return selector(result);
@ -57,7 +57,7 @@ module.exports = {
if (typeof selector === "string") {
return result.value()[selector];
} else {
return selector(result.value());
return selector(result.value(), context);
}
}
});

@ -0,0 +1,36 @@
"use strict";
const Promise = require("bluebird");
const memoizee = require("memoizee");
// FIXME: Figure out a reasonable way to make this symbol its own (conflict-free) package, given that it'll be used all across both sysquery and CVM
const All = require("../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)
module.exports = function evaluateAndPick({ command, selectResult, selectID, many }) {
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 if (many === true) {
// NOTE: This produces nested arrays! One array for each input ID.
return items.filter((item) => selectID(item) === id);
} else {
// TODO: Can this be more performant? Currently it is a nested loop
return items.find((item) => selectID(item) === id);
}
});
});
};
};

@ -0,0 +1,12 @@
"use strict";
const Promise = require("bluebird");
const All = require("../graphql-interface/symbols/all");
module.exports = async function mapFromSource(source, ids, mapper) {
let results = (ids === All || ids == null)
? await source.load(All)
: await Promise.map(ids, (id) => source.load(id));
return results.map(mapper);
};

@ -1,64 +1,183 @@
"use strict";
const Promise = require("bluebird");
const DataLoader = require("dataloader");
const dlayerSource = require("../dlayer-source");
const evaluateAndPick = require("../evaluate-and-pick");
const mapFromSource = require("../map-from-source");
const lvm = require("../exec-lvm");
const All = require("../graphql-interface/symbols/all");
module.exports = {
name: "LVM",
initialize: function () {
let types = {
"sysquery.lvm.PhysicalVolume": function PhysicalVolume({ path }) {
},
"sysquery.lvm.VolumeGroup": function VolumeGroup({ name }) {
},
"sysquery.lvm.LogicalVolume": function LogicalVolume({ path }) {
}
makeContext: function () {
return {
physicalVolumes: new DataLoader(evaluateAndPick({
command: lvm.getPhysicalVolumes,
selectResult: (result) => result.volumes,
selectID: (device) => device.path
})),
volumeGroups: new DataLoader(evaluateAndPick({
command: lvm.getVolumeGroups,
selectResult: (result) => result.groups,
selectID: (group) => group.name
})),
logicalVolumes: new DataLoader(evaluateAndPick({
command: lvm.getLogicalVolumes,
selectResult: (result) => result.volumes,
selectID: (volume) => volume.path
}))
};
},
root: {
resources: {
lvm: {
physicalVolumes: ({ paths }, { physicalVolumes, $make }) => {
return mapFromSource(physicalVolumes, paths, (volume) => {
return $make("sysquery.lvm.PhysicalVolume", { path: volume.path });
});
},
volumeGroups: ({ names }, { volumeGroups, $make }) => {
return mapFromSource(volumeGroups, names, (group) => {
return $make("sysquery.lvm.VolumeGroup", { name: group.name });
});
},
logicalVolumes: ({ paths }, { logicalVolumes, $make }) => {
// FIXME: Aren't these scoped to a volume group?
return mapFromSource(logicalVolumes, paths, (volume) => {
return $make("sysquery.lvm.LogicalVolume", { path: volume.path });
});
}
}
}
},
extensions: {
"sysquery.core.BlockDevice": {
lvmPhysicalVolume: async function (_, { physicalVolumes, $getProperty, $make }) {
let volume = physicalVolumes.get(await $getProperty(this, "path"));
return {
sources: () => {
return {
physicalVolumes: ,
volumeGroups: ,
logicalVolumes:
};
},
types: types,
extensions: {
"sysquery.core.BlockDevice": {
lvmPhysicalVolume: function (_, { $getProperty, sources }) {
if (volume != null) {
return $make("sysquery.lvm.PhysicalVolume", { path: volume.path });
} else {
return null;
}
}
}
},
types: {
"sysquery.lvm.PhysicalVolume": function PhysicalVolume({ path }) {
return dlayerSource.withSources({
$sources: {
physicalVolumes: {
[dlayerSource.ID]: path,
path: "path",
format: "format",
totalSpace: "totalSpace",
freeSpace: "freeSpace",
isExported: "isExported",
isMissing: "isMissing",
isAllocatable: "isAllocatable",
isDuplicate: "isDuplicate",
isUsed: "isUsed",
volumeGroup: (volume, { $make }) => {
return $make("sysquery.lvm.VolumeGroup", { name: volume.volumeGroup });
}
}
}
});
},
"sysquery.lvm.VolumeGroup": function VolumeGroup({ name }) {
return dlayerSource.withSources({
physicalVolumes: function (_args, { physicalVolumes, $make }) {
return Promise.try(() => {
return physicalVolumes.load(All);
}).filter((volume) => {
return (volume.volumeGroup === name);
}).map((volume) => {
return $make("sysquery.lvm.PhysicalVolume", { path: volume.path });
});
},
logicalVolumes: function (_args, { logicalVolumes, $make }) {
return Promise.try(() => {
return logicalVolumes.load(All);
}).filter((volume) => {
return (volume.volumeGroup === name);
}).map((volume) => {
return $make("sysquery.lvm.LogicalVolume", { path: volume.path });
});
},
$sources: {
volumeGroups: {
[dlayerSource.ID]: name,
name: "name",
totalSpace: "totalSpace",
freeSpace: "freeSpace",
physicalVolumeCount: "physicalVolumeCount",
logicalVolumeCount: "logicalVolumeCount",
snapshotCount: "snapshotCount",
isReadOnly: "isReadOnly",
isResizeable: "isResizeable",
isExported: "isExported",
isIncomplete: "isIncomplete",
allocationPolicy: "allocationPolicy",
mode: "mode"
}
}
},
root: {
resources: {
lvm: {
physicalVolumes: ({ paths }, { sources }) => {
return makeTypeFromSource({
source: sources.physicalVolumes,
ids: paths,
make: (result) => types.PhysicalVolume({ path: result.path }),
});
},
volumeGroups: ({ names }, { sources }) => {
return makeTypeFromSource({
source: sources.volumeGroups,
ids: names,
make: (result) => types.VolumeGroup({ name: result.name }),
});
},
logicalVolumes: ({ paths }, { sources }) => {
// FIXME: Aren't these scoped to a volume group?
return makeTypeFromSource({
source: sources.logicalVolumes,
ids: paths,
make: (result) => types.LogicalVolume({ path: result.path }),
});
});
},
"sysquery.lvm.LogicalVolume": function LogicalVolume({ path }) {
return dlayerSource.withSources({
$sources: {
logicalVolumes: {
[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, { $make }) => {
return $make("sysquery.lvm.VolumeGroup", { name: volume.volumeGroup });
}
}
}
}
};
});
}
}
};

@ -2904,10 +2904,10 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dlayer@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dlayer/-/dlayer-0.1.0.tgz#cef49d283e1a9d83a8bd69a4c01d468ddd0b898c"
integrity sha512-UrdQdihmoNbWv49YFD/MBspaS2ILEfrNBoIVvZb1fhwvnPoRQ3ZOAbyiyV1sdfTrCASWVMz4i2NFczOVoVWJxw==
dlayer@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dlayer/-/dlayer-0.1.2.tgz#3dbc43e55746988480bc8a4926aa523909661284"
integrity sha512-ZB709Ld/2TxUHMAKPfbvW2f0rscQAnu65j4/Nw8YRDpATFZd/NGmIXtOyEmYV7D904aLZLeKCaerfY+4Bu0i7Q==
dependencies:
"@joepie91/result" "^0.1.0"
bluebird "^3.4.6"

Loading…
Cancel
Save