You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
5.5 KiB
JavaScript
223 lines
5.5 KiB
JavaScript
5 years ago
|
"use strict";
|
||
|
|
||
|
const Promise = require("bluebird");
|
||
|
const graphql = require("graphql");
|
||
|
const fs = require("fs");
|
||
|
const path = require("path");
|
||
|
|
||
|
const matchOrError = require("../match-or-error");
|
||
|
const upperSnakeCase = require("../upper-snake-case");
|
||
|
const All = require("../graphql/symbols/all");
|
||
|
const createGraphQLInterface = require("../graphql/index");
|
||
|
const {ID, LocalProperties, createDataObject} = require("../graphql/data-object");
|
||
|
|
||
|
const createLoaders = require("./loaders");
|
||
|
|
||
|
/* FIXME: This seems to be added into a global registry somehow? How to specify this explicitly on a query without relying on globals? */
|
||
|
new graphql.GraphQLScalarType({
|
||
|
name: "ByteSize",
|
||
|
description: "A value that represents a value on a byte scale",
|
||
|
serialize: (value) => {
|
||
|
return JSON.stringify(value);
|
||
|
},
|
||
|
parseValue: (value) => {
|
||
|
return JSON.parse(value);
|
||
|
},
|
||
|
parseLiteral: (value) => {
|
||
|
return JSON.parse(value);
|
||
|
},
|
||
|
});
|
||
|
|
||
|
new graphql.GraphQLScalarType({
|
||
|
name: "TimeSize",
|
||
|
description: "A value that represents a value on a time scale",
|
||
|
serialize: (value) => {
|
||
|
return JSON.stringify(value);
|
||
|
},
|
||
|
parseValue: (value) => {
|
||
|
return JSON.parse(value);
|
||
|
},
|
||
|
parseLiteral: (value) => {
|
||
|
return JSON.parse(value);
|
||
|
},
|
||
|
});
|
||
|
|
||
|
let schema = graphql.buildSchema(fs.readFileSync(path.resolve(__dirname, "../schemas/main.gql"), "utf8"));
|
||
|
|
||
|
function createBlockDevice({ name, path }) {
|
||
|
if (name != null) {
|
||
|
path = `/dev/${name}`;
|
||
|
} else if (path != null) {
|
||
|
let match = matchOrError(/^\/dev\/(.+)$/, path);
|
||
|
name = match[0];
|
||
|
}
|
||
|
|
||
|
/* FIXME: parent */
|
||
|
|
||
|
return createDataObject({
|
||
|
[LocalProperties]: {
|
||
|
path: path
|
||
|
},
|
||
|
lsblk: {
|
||
|
[ID]: name,
|
||
|
name: "name",
|
||
|
size: "size",
|
||
|
mountpoint: "mountpoint",
|
||
|
deviceNumber: "deviceNumber",
|
||
|
removable: "removable",
|
||
|
readOnly: "readOnly",
|
||
|
children: (device) => {
|
||
|
return device.children.map((child) => {
|
||
|
return createBlockDevice({ name: child.name });
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createPhysicalVolume({ path }) {
|
||
|
return createDataObject({
|
||
|
[LocalProperties]: {
|
||
|
path: path,
|
||
|
blockDevice: () => {
|
||
|
return createBlockDevice({ path: path });
|
||
|
}
|
||
|
},
|
||
|
lvmPhysicalVolumes: {
|
||
|
[ID]: path,
|
||
|
volumeGroup: (volume) => {
|
||
|
if (volume.volumeGroup != null) {
|
||
|
return createVolumeGroup({ name: volume.volumeGroup });
|
||
|
}
|
||
|
},
|
||
|
format: "format",
|
||
|
size: "totalSpace",
|
||
|
freeSpace: "freeSpace",
|
||
|
duplicate: "isDuplicate",
|
||
|
allocatable: "isAllocatable",
|
||
|
used: "isUsed",
|
||
|
exported: "isExported",
|
||
|
missing: "isMissing"
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createVolumeGroup({ name }) {
|
||
|
return createDataObject({
|
||
|
[LocalProperties]: {
|
||
|
name: name
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createDrive({ path }) {
|
||
|
return createDataObject({
|
||
|
[LocalProperties]: {
|
||
|
path: path,
|
||
|
blockDevice: () => {
|
||
|
return createBlockDevice({ path: path });
|
||
|
},
|
||
|
/* FIXME: allBlockDevices, for representing every single block device that's hosted on this physical drive, linearly. Need to figure out how that works with representation of mdraid arrays, LVM volumes, etc. */
|
||
|
},
|
||
|
smartctlScan: {
|
||
|
[ID]: path,
|
||
|
interface: "interface"
|
||
|
},
|
||
|
smartctlInfo: {
|
||
|
[ID]: path,
|
||
|
model: "model",
|
||
|
modelFamily: "modelFamily",
|
||
|
smartAvailable: "smartAvailable",
|
||
|
smartEnabled: "smartEnabled",
|
||
|
serialNumber: "serialNumber",
|
||
|
wwn: "wwn",
|
||
|
firmwareVersion: "firmwareVersion",
|
||
|
size: "size",
|
||
|
rpm: "rpm",
|
||
|
logicalSectorSize: (device) => device.sectorSizes.logical,
|
||
|
physicalSectorSize: (device) => device.sectorSizes.physical,
|
||
|
formFactor: "formFactor",
|
||
|
ataVersion: "ataVersion",
|
||
|
sataVersion: "sataVersion"
|
||
|
},
|
||
|
smartctlAttributes: {
|
||
|
[ID]: path,
|
||
|
smartAttributes: (attributes) => {
|
||
|
return attributes.map((attribute) => {
|
||
|
return Object.assign({}, attribute, {
|
||
|
type: upperSnakeCase(attribute.type),
|
||
|
updatedWhen: upperSnakeCase(attribute.updatedWhen)
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
smartHealth: (attributes) => {
|
||
|
let failed = attributes.filter((item) => {
|
||
|
return (item.failingNow === true || item.failedBefore === true);
|
||
|
});
|
||
|
|
||
|
let deteriorating = attributes.filter((item) => {
|
||
|
return (item.type === "preFail" && item.worstValueSeen < 100);
|
||
|
});
|
||
|
|
||
|
if (failed.length > 0) {
|
||
|
return "FAILING";
|
||
|
} else if (deteriorating.length > 0) {
|
||
|
return "DETERIORATING";
|
||
|
} else {
|
||
|
return "HEALTHY";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
module.exports = function () {
|
||
|
return createGraphQLInterface(schema, { loaderFactory: createLoaders }, {
|
||
|
hardware: {
|
||
|
drives: function ({ paths }, { data }) {
|
||
|
return Promise.try(() => {
|
||
|
if (paths != null) {
|
||
|
return data.smartctlScan.loadMany(paths);
|
||
|
} else {
|
||
|
return data.smartctlScan.load(All);
|
||
|
}
|
||
|
}).then((devices) => {
|
||
|
return devices.map((device) => {
|
||
|
return createDrive({ path: device.path });
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
resources: {
|
||
|
blockDevices: function ({ names }, { data }) {
|
||
|
return Promise.try(() => {
|
||
|
if (names != null) {
|
||
|
return data.lsblk.loadMany(names);
|
||
|
} else {
|
||
|
return data.lsblk.load(All);
|
||
|
}
|
||
|
}).then((devices) => {
|
||
|
return devices.map((device) => {
|
||
|
return createBlockDevice({ name: device.name });
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
lvm: {
|
||
|
physicalVolumes: function ({ paths }, { data }) {
|
||
|
return Promise.try(() => {
|
||
|
if (paths != null) {
|
||
|
return data.lvmPhysicalVolumes.loadMany(paths);
|
||
|
} else {
|
||
|
return data.lvmPhysicalVolumes.load(All);
|
||
|
}
|
||
|
}).then((volumes) => {
|
||
|
return volumes.map((volume) => {
|
||
|
return createPhysicalVolume({ path: volume.path });
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|