"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"; } } } }); }; };