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.
150 lines
4.9 KiB
JavaScript
150 lines
4.9 KiB
JavaScript
"use strict";
|
|
|
|
const Promise = require("bluebird");
|
|
|
|
const {createDataObject, LocalProperties, ID, Dynamic} = require("../../packages/graphql-interface/data-object");
|
|
const upperSnakeCase = require("../../packages/upper-snake-case");
|
|
const treecutter = require("../../packages/treecutter");
|
|
const deviceNameFromPath = require("../../util/device-name-from-path");
|
|
|
|
/* TO IMPLEMENT:
|
|
- resolveProperty
|
|
- resolveProperties
|
|
- resolveDataSource
|
|
- Dynamic
|
|
*/
|
|
|
|
module.exports = function (types) {
|
|
return function Drive({ path }) {
|
|
return createDataObject({
|
|
[LocalProperties]: {
|
|
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. */
|
|
},
|
|
[Dynamic]: {
|
|
// FIXME: namespaces
|
|
blockDevice: (_, { resolveProperty }) => {
|
|
return Promise.try(() => {
|
|
return resolveProperty("interface");
|
|
}).then((interface_) => {
|
|
if (interface_ === "nvme") {
|
|
/* NVMe drives do not have a single block device, they have zero or more namespaces */
|
|
return null;
|
|
} else {
|
|
return types.BlockDevice({ path: path });
|
|
}
|
|
});
|
|
|
|
},
|
|
allBlockDevices: ({ type }, { resolveProperty, resolveDataSource }) => {
|
|
// FIXME: Figure out how to semantically represent that data cannot be stored directly onto an NVMe device (only onto a namespace), but *can* be directly stored on a *non-NVMe* device... usually, anyway.
|
|
|
|
return Promise.try(() => {
|
|
return resolveProperty("interface");
|
|
}).then((interface_) => {
|
|
if (interface_ === "nvme") {
|
|
// Dynamic data source lookup: nvme list-ns -> Drive
|
|
return Promise.try(() => {
|
|
return resolveDataSource("nvmeListNamespaces", path);
|
|
}).map((namespaceId) => {
|
|
return `${path}n${namespaceId}`;
|
|
});
|
|
} else {
|
|
return [ path ];
|
|
}
|
|
}).map((rootPath) => {
|
|
return resolveDataSource("lsblk", { path: rootPath });
|
|
}).then((blockDeviceTrees) => {
|
|
let blockDevices = treecutter.flatten(blockDeviceTrees)
|
|
.map((device) => types.BlockDevice(device));
|
|
// MARKER: Find a way to reassemble this tree on the client side, for display
|
|
// MARKER: Why are most of the mounts (erroneously) empty?
|
|
|
|
if (type != null) {
|
|
return Promise.filter(blockDevices, (device) => {
|
|
return Promise.try(() => {
|
|
return resolveProperty("type", device.item);
|
|
}).then((deviceType) => {
|
|
return (deviceType === type);
|
|
});
|
|
});
|
|
} else {
|
|
return blockDevices;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
lsblk: {
|
|
[ID]: { path },
|
|
// TODO: Implement [DependsOn], for cases where a source data mapper depends on data from more than one source, so it can reference properties defined elsewhere?
|
|
// FIXME: Figure out a nice way to make a source lookup conditional upon something else (like only do a `lsblk` if not an NVMe drive, and for NVMe drives return a hardcoded thing)
|
|
// allBlockDevices: function (rootDevice, { type }, context) {
|
|
// let devices = treecutter.flatten([rootDevice])
|
|
// .map((device) => types.BlockDevice({ name: device.name }));
|
|
|
|
// if (type != null) {
|
|
// return Promise.filter(devices, (device) => {
|
|
// return Promise.try(() => {
|
|
// return device.type({}, context);
|
|
// }).then((deviceType) => {
|
|
// return (deviceType === type);
|
|
// });
|
|
// });
|
|
// } else {
|
|
// return devices;
|
|
// }
|
|
// }
|
|
},
|
|
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";
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
};
|